intelfb: support 945GME (as used in ASUS Eee 901)
[safe/jmp/linux-2.6] / drivers / video / intelfb / intelfbhw.c
index fe38df8..8b26b27 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Linux framebuffer driver for Intel(R) 865G integrated graphics chips.
  *
- * Copyright © 2002, 2003 David Dawes <dawes@xfree86.org>
+ * Copyright Â© 2002, 2003 David Dawes <dawes@xfree86.org>
  *                   2004 Sylvain Meyer
  *
  * This driver consists of two parts.  The first part (intelfbdrv.c) provides
@@ -143,6 +143,24 @@ int intelfbhw_get_chipset(struct pci_dev *pdev, struct intelfb_info *dinfo)
                dinfo->mobile = 1;
                dinfo->pll_index = PLLS_I9xx;
                return 0;
+       case PCI_DEVICE_ID_INTEL_945GME:
+               dinfo->name = "Intel(R) 945GME";
+               dinfo->chipset = INTEL_945GME;
+               dinfo->mobile = 1;
+               dinfo->pll_index = PLLS_I9xx;
+               return 0;
+       case PCI_DEVICE_ID_INTEL_965G:
+               dinfo->name = "Intel(R) 965G";
+               dinfo->chipset = INTEL_965G;
+               dinfo->mobile = 0;
+               dinfo->pll_index = PLLS_I9xx;
+               return 0;
+       case PCI_DEVICE_ID_INTEL_965GM:
+               dinfo->name = "Intel(R) 965GM";
+               dinfo->chipset = INTEL_965GM;
+               dinfo->mobile = 1;
+               dinfo->pll_index = PLLS_I9xx;
+               return 0;
        default:
                return 1;
        }
@@ -174,7 +192,10 @@ int intelfbhw_get_memory(struct pci_dev *pdev, int *aperture_size,
        case PCI_DEVICE_ID_INTEL_915GM:
        case PCI_DEVICE_ID_INTEL_945G:
        case PCI_DEVICE_ID_INTEL_945GM:
-               /* 915 and 945 chipsets support a 256MB aperture.
+       case PCI_DEVICE_ID_INTEL_945GME:
+       case PCI_DEVICE_ID_INTEL_965G:
+       case PCI_DEVICE_ID_INTEL_965GM:
+               /* 915, 945 and 965 chipsets support a 256MB aperture.
                   Aperture size is determined by inspected the
                   base address of the aperture. */
                if (pci_resource_start(pdev, 2) & 0x08000000)
@@ -317,6 +338,14 @@ int intelfbhw_validate_mode(struct intelfb_info *dinfo,
                        var->yres, VACTIVE_MASK + 1);
                return 1;
        }
+       if (var->xres < 4) {
+               WRN_MSG("X resolution too small (%d vs 4).\n", var->xres);
+               return 1;
+       }
+       if (var->yres < 4) {
+               WRN_MSG("Y resolution too small (%d vs 4).\n", var->yres);
+               return 1;
+       }
 
        /* Check for doublescan modes. */
        if (var->vmode & FB_VMODE_DOUBLE) {
@@ -324,6 +353,11 @@ int intelfbhw_validate_mode(struct intelfb_info *dinfo,
                return 1;
        }
 
+       if ((var->vmode & FB_VMODE_INTERLACED) && (var->yres & 1)) {
+               WRN_MSG("Odd number of lines in interlaced mode\n");
+               return 1;
+       }
+
        /* Check if clock is OK. */
        tmp = 1000000000 / var->pixclock;
        if (tmp < MIN_CLOCK) {
@@ -363,7 +397,7 @@ int intelfbhw_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
 
        dinfo->vsync.pan_offset = offset;
        if ((var->activate & FB_ACTIVATE_VBL) &&
-           !intelfbhw_enable_irq(dinfo, 0))
+           !intelfbhw_enable_irq(dinfo))
                dinfo->vsync.pan_display = 1;
        else {
                dinfo->vsync.pan_display = 0;
@@ -434,14 +468,14 @@ void intelfbhw_setcolreg(struct intelfb_info *dinfo, unsigned regno,
                         unsigned red, unsigned green, unsigned blue,
                         unsigned transp)
 {
+       u32 palette_reg = (dinfo->pipe == PIPE_A) ?
+                         PALETTE_A : PALETTE_B;
+
 #if VERBOSE > 0
        DBG_MSG("intelfbhw_setcolreg: %d: (%d, %d, %d)\n",
                regno, red, green, blue);
 #endif
 
-       u32 palette_reg = (dinfo->pipe == PIPE_A) ?
-                         PALETTE_A : PALETTE_B;
-
        OUTREG(palette_reg + (regno << 2),
               (red << PALETTE_8_RED_SHIFT) |
               (green << PALETTE_8_GREEN_SHIFT) |
@@ -1127,6 +1161,8 @@ int intelfbhw_mode_to_hw(struct intelfb_info *dinfo,
                hblank_end);
 
        vactive = var->yres;
+       if (var->vmode & FB_VMODE_INTERLACED)
+               vactive--; /* the chip adds 2 halflines automatically */
        vsync_start = vactive + var->lower_margin;
        vsync_end = vsync_start + var->vsync_len;
        vtotal = vsync_end + var->upper_margin;
@@ -1225,7 +1261,7 @@ int intelfbhw_program_mode(struct intelfb_info *dinfo,
        u32 tmp;
        const u32 *dpll, *fp0, *fp1, *pipe_conf;
        const u32 *hs, *ht, *hb, *vs, *vt, *vb, *ss;
-       u32 dpll_reg, fp0_reg, fp1_reg, pipe_conf_reg;
+       u32 dpll_reg, fp0_reg, fp1_reg, pipe_conf_reg, pipe_stat_reg;
        u32 hsync_reg, htotal_reg, hblank_reg;
        u32 vsync_reg, vtotal_reg, vblank_reg;
        u32 src_size_reg;
@@ -1266,6 +1302,7 @@ int intelfbhw_program_mode(struct intelfb_info *dinfo,
                fp0_reg = FPB0;
                fp1_reg = FPB1;
                pipe_conf_reg = PIPEBCONF;
+               pipe_stat_reg = PIPEBSTAT;
                hsync_reg = HSYNC_B;
                htotal_reg = HTOTAL_B;
                hblank_reg = HBLANK_B;
@@ -1289,6 +1326,7 @@ int intelfbhw_program_mode(struct intelfb_info *dinfo,
                fp0_reg = FPA0;
                fp1_reg = FPA1;
                pipe_conf_reg = PIPEACONF;
+               pipe_stat_reg = PIPEASTAT;
                hsync_reg = HSYNC_A;
                htotal_reg = HTOTAL_A;
                hblank_reg = HBLANK_A;
@@ -1305,8 +1343,8 @@ int intelfbhw_program_mode(struct intelfb_info *dinfo,
 
        count = 0;
        do {
-               tmp_val[count%3] = INREG(0x70000);
-               if ((tmp_val[0] == tmp_val[1]) && (tmp_val[1]==tmp_val[2]))
+               tmp_val[count % 3] = INREG(PIPEA_DSL);
+               if ((tmp_val[0] == tmp_val[1]) && (tmp_val[1] == tmp_val[2]))
                        break;
                count++;
                udelay(1);
@@ -1375,6 +1413,17 @@ int intelfbhw_program_mode(struct intelfb_info *dinfo,
        OUTREG(vtotal_reg, *vt);
        OUTREG(src_size_reg, *ss);
 
+       switch (dinfo->info->var.vmode & (FB_VMODE_INTERLACED |
+                                         FB_VMODE_ODD_FLD_FIRST)) {
+       case FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST:
+               OUTREG(pipe_stat_reg, 0xFFFF | PIPESTAT_FLD_EVT_ODD_EN);
+               break;
+       case FB_VMODE_INTERLACED: /* even lines first */
+               OUTREG(pipe_stat_reg, 0xFFFF | PIPESTAT_FLD_EVT_EVEN_EN);
+               break;
+       default:                /* non-interlaced */
+               OUTREG(pipe_stat_reg, 0xFFFF); /* clear all status bits only */
+       }
        /* Enable pipe */
        OUTREG(pipe_conf_reg, *pipe_conf | PIPECONF_ENABLE);
 
@@ -1940,72 +1989,72 @@ void intelfbhw_cursor_reset(struct intelfb_info *dinfo)
        }
 }
 
-static irqreturn_t
-intelfbhw_irq(int irq, void *dev_id) {
-       int handled = 0;
+static irqreturn_t intelfbhw_irq(int irq, void *dev_id)
+{
        u16 tmp;
-       struct intelfb_info *dinfo = (struct intelfb_info *)dev_id;
+       struct intelfb_info *dinfo = dev_id;
 
        spin_lock(&dinfo->int_lock);
 
        tmp = INREG16(IIR);
-       tmp &= VSYNC_PIPE_A_INTERRUPT;
+       if (dinfo->info->var.vmode & FB_VMODE_INTERLACED)
+               tmp &= PIPE_A_EVENT_INTERRUPT;
+       else
+               tmp &= VSYNC_PIPE_A_INTERRUPT; /* non-interlaced */
 
        if (tmp == 0) {
                spin_unlock(&dinfo->int_lock);
-               return IRQ_RETVAL(handled);
+               return IRQ_RETVAL(0); /* not us */
        }
 
-       OUTREG16(IIR, tmp);
+       /* clear status bits 0-15 ASAP and don't touch bits 16-31 */
+       OUTREG(PIPEASTAT, INREG(PIPEASTAT));
 
-       if (tmp & VSYNC_PIPE_A_INTERRUPT) {
-               dinfo->vsync.count++;
-               if (dinfo->vsync.pan_display) {
-                       dinfo->vsync.pan_display = 0;
-                       OUTREG(DSPABASE, dinfo->vsync.pan_offset);
-               }
-               wake_up_interruptible(&dinfo->vsync.wait);
-               handled = 1;
+       OUTREG16(IIR, tmp);
+       if (dinfo->vsync.pan_display) {
+               dinfo->vsync.pan_display = 0;
+               OUTREG(DSPABASE, dinfo->vsync.pan_offset);
        }
 
+       dinfo->vsync.count++;
+       wake_up_interruptible(&dinfo->vsync.wait);
+
        spin_unlock(&dinfo->int_lock);
 
-       return IRQ_RETVAL(handled);
+       return IRQ_RETVAL(1);
 }
 
-int
-intelfbhw_enable_irq(struct intelfb_info *dinfo, int reenable) {
-
+int intelfbhw_enable_irq(struct intelfb_info *dinfo)
+{
+       u16 tmp;
        if (!test_and_set_bit(0, &dinfo->irq_flags)) {
                if (request_irq(dinfo->pdev->irq, intelfbhw_irq, IRQF_SHARED,
-                    "intelfb", dinfo)) {
+                               "intelfb", dinfo)) {
                        clear_bit(0, &dinfo->irq_flags);
                        return -EINVAL;
                }
 
                spin_lock_irq(&dinfo->int_lock);
-               OUTREG16(HWSTAM, 0xfffe);
-               OUTREG16(IMR, 0x0);
-               OUTREG16(IER, VSYNC_PIPE_A_INTERRUPT);
-               spin_unlock_irq(&dinfo->int_lock);
-       } else if (reenable) {
-               u16 ier;
-
+               OUTREG16(HWSTAM, 0xfffe); /* i830 DRM uses ffff */
+               OUTREG16(IMR, 0);
+       } else
                spin_lock_irq(&dinfo->int_lock);
-               ier = INREG16(IER);
-               if ((ier & VSYNC_PIPE_A_INTERRUPT)) {
-                       DBG_MSG("someone disabled the IRQ [%08X]\n", ier);
-                       OUTREG(IER, VSYNC_PIPE_A_INTERRUPT);
-               }
-               spin_unlock_irq(&dinfo->int_lock);
+
+       if (dinfo->info->var.vmode & FB_VMODE_INTERLACED)
+               tmp = PIPE_A_EVENT_INTERRUPT;
+       else
+               tmp = VSYNC_PIPE_A_INTERRUPT; /* non-interlaced */
+       if (tmp != INREG16(IER)) {
+               DBG_MSG("changing IER to 0x%X\n", tmp);
+               OUTREG16(IER, tmp);
        }
+
+       spin_unlock_irq(&dinfo->int_lock);
        return 0;
 }
 
-void
-intelfbhw_disable_irq(struct intelfb_info *dinfo) {
-       u16 tmp;
-
+void intelfbhw_disable_irq(struct intelfb_info *dinfo)
+{
        if (test_and_clear_bit(0, &dinfo->irq_flags)) {
                if (dinfo->vsync.pan_display) {
                        dinfo->vsync.pan_display = 0;
@@ -2016,8 +2065,7 @@ intelfbhw_disable_irq(struct intelfb_info *dinfo) {
                OUTREG16(IMR, 0xffff);
                OUTREG16(IER, 0x0);
 
-               tmp = INREG16(IIR);
-               OUTREG16(IIR, tmp);
+               OUTREG16(IIR, INREG16(IIR)); /* clear IRQ requests */
                spin_unlock_irq(&dinfo->int_lock);
 
                free_irq(dinfo->pdev->irq, dinfo);
@@ -2038,7 +2086,7 @@ int intelfbhw_wait_for_vsync(struct intelfb_info *dinfo, u32 pipe)
                        return -ENODEV;
        }
 
-       ret = intelfbhw_enable_irq(dinfo, 0);
+       ret = intelfbhw_enable_irq(dinfo);
        if (ret)
                return ret;
 
@@ -2048,7 +2096,6 @@ int intelfbhw_wait_for_vsync(struct intelfb_info *dinfo, u32 pipe)
        if (ret < 0)
                return ret;
        if (ret == 0) {
-               intelfbhw_enable_irq(dinfo, 1);
                DBG_MSG("wait_for_vsync timed out!\n");
                return -ETIMEDOUT;
        }