diff options
Diffstat (limited to 'drivers/video/fbdev/aty')
| -rw-r--r-- | drivers/video/fbdev/aty/Makefile | 15 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/ati_ids.h | 214 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/aty128fb.c | 2591 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/atyfb.h | 369 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/atyfb_base.c | 4029 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/mach64_accel.c | 430 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/mach64_ct.c | 649 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/mach64_cursor.c | 225 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/mach64_gx.c | 910 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/radeon_accel.c | 328 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/radeon_backlight.c | 221 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/radeon_base.c | 2568 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/radeon_i2c.c | 167 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/radeon_monitor.c | 1052 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/radeon_pm.c | 2906 | ||||
| -rw-r--r-- | drivers/video/fbdev/aty/radeonfb.h | 634 | 
16 files changed, 17308 insertions, 0 deletions
diff --git a/drivers/video/fbdev/aty/Makefile b/drivers/video/fbdev/aty/Makefile new file mode 100644 index 00000000000..a6cc0e9ec79 --- /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 00000000000..3e9d28bcd9f --- /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 00000000000..52108be69e7 --- /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 00000000000..1f39a62f899 --- /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 00000000000..c3d0074a32d --- /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 00000000000..182bd680141 --- /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 00000000000..51f29d627ce --- /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 00000000000..2fa0317ab3c --- /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 "../core/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 00000000000..10c988aef58 --- /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 00000000000..a469a3d6edc --- /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 00000000000..db572df7e1e --- /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 00000000000..26d80a4486f --- /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 00000000000..ab1d0fd7631 --- /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 00000000000..bc078d50d8f --- /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 00000000000..46a12f1a93c --- /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 00000000000..cb846044f57 --- /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__ */  | 
