Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  atyfb_base.c   Sprache: C

 
/*
 *  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/aperture.h>
#include <linux/compat.h>
#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 "../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

/*
 * 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...) no_printk(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_PMAC_BACKLIGHT) || defined(CONFIG_FB_ATY_GENERIC_LCD) || \
defined(CONFIG_FB_ATY_BACKLIGHT) || defined (CONFIG_PPC_PMAC)
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);
 }
}
#else /* defined(CONFIG_PMAC_BACKLIGHT) || defined(CONFIG_FB_ATY_BACKLIGHT) ||
 defined(CONFIG_FB_ATY_GENERIC_LCD) || defined(CONFIG_PPC_PMAC) */

void aty_st_lcd(int index, u32 val, const struct atyfb_par *par)
{ }

u32 aty_ld_lcd(int index, const struct atyfb_par *par)
{
 return 0;
}
#endif /* defined(CONFIG_PMAC_BACKLIGHT) || defined(CONFIG_FB_ATY_BACKLIGHT) ||
  defined (CONFIG_FB_ATY_GENERIC_LCD) || defined(CONFIG_PPC_PMAC) */


#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 const 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 CONFIG_COMPAT
static int atyfb_compat_ioctl(struct fb_info *info, u_int cmd, u_long arg)
{
 return atyfb_ioctl(info, cmd, (u_long)compat_ptr(arg));
}
#endif

#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 const 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_DEFAULT_IOMEM_OPS_RDWR,
 .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,
#ifdef CONFIG_COMPAT
 .fb_compat_ioctl = atyfb_compat_ioctl,
#endif
 .fb_fillrect = atyfb_fillrect,
 .fb_copyarea = atyfb_copyarea,
 .fb_imageblit = atyfb_imageblit,
#ifdef __sparc__
 .fb_mmap = atyfb_mmap,
#else
 __FB_DEFAULT_IOMEM_OPS_MMAP,
#endif
 .fb_sync = atyfb_sync,
};

static bool noaccel;
static bool nomtrr;
static int vram;
static int pll;
static int mclk;
static int xclk;
static int comp_sync = -1;
static char *mode;
static int backlight = IS_BUILTIN(CONFIG_PMAC_BACKLIGHT);

#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 */
};

/*
 * Last page of 8 MB (4 MB on ISA) aperture is MMIO,
 * unless the auxiliary register aperture is used.
 */

static void aty_fudge_framebuffer_len(struct fb_info *info)
{
 struct atyfb_par *par = (struct atyfb_par *) info->par;

 if (!par->aux_start &&
     (info->fix.smem_len == 0x800000 ||
      (par->bus_type == ISA && info->fix.smem_len == 0x400000)))
  info->fix.smem_len -= GUI_RESERVE;
}

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;
 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;
 }

 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) {
  u32 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) {
 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) {
  atyfb_ops.fb_sync = atyfb_sync;
  info->flags &= ~FBINFO_HWACCEL_DISABLED;
 } else {
  atyfb_ops.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 */
#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) {
   pr_cont("\n");
   printk("debug atyfb: 0x%04X: ", base + i);
  }
  pr_cont(" %08X", aty_ld_le32(i, par));
 }
 pr_cont("\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) {
   pr_cont("\n");
   printk("debug atyfb: 0x%02X: ", base + i);
  }
  if (i % 4 == 0)
   pr_cont(" ");
  pr_cont("%02X", aty_ld_pll_ct(i, par));
 }
 pr_cont("\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;
    pr_cont("\ndebug atyfb: 0x%04X: ",
           lt_lcd_regs[i]);
    pr_cont(" %08X", aty_ld_lcd(i, par));
   }
  } else {
   for (i = 0; i < 64; i++) {
    if (i % 4 == 0)
     pr_cont("\ndebug atyfb: 0x%02X: ",
            base + i);
    pr_cont(" %08X", aty_ld_lcd(i, par));
   }
  }
  pr_cont("\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 = { 0 };
   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_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_late(struct device *dev, pm_message_t state)
{
 struct pci_dev *pdev = to_pci_dev(dev);
 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.
 */


#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;
 }
#endif

 console_unlock();

 pdev->dev.power.power_state = state;

 return 0;
}

static int __maybe_unused atyfb_pci_suspend(struct device *dev)
{
 return atyfb_pci_suspend_late(dev, PMSG_SUSPEND);
}

static int __maybe_unused atyfb_pci_hibernate(struct device *dev)
{
 return atyfb_pci_suspend_late(dev, PMSG_HIBERNATE);
}

static int __maybe_unused atyfb_pci_freeze(struct device *dev)
{
 return atyfb_pci_suspend_late(dev, PMSG_FREEZE);
}

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 __maybe_unused atyfb_pci_resume(struct device *dev)
{
 struct pci_dev *pdev = to_pci_dev(dev);
 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;
}

static const struct dev_pm_ops atyfb_pci_pm_ops = {
#ifdef CONFIG_PM_SLEEP
 .suspend = atyfb_pci_suspend,
 .resume  = atyfb_pci_resume,
 .freeze  = atyfb_pci_freeze,
 .thaw  = atyfb_pci_resume,
 .poweroff = atyfb_pci_hibernate,
 .restore = atyfb_pci_resume,
#endif /* CONFIG_PM_SLEEP */
};

#endif /*  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 = backlight_get_brightness(bd);

 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 const struct backlight_ops aty_bl_data = {
 .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->device, 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;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=93 G=93

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge