root/src/add-ons/accelerants/matrox/engine/mga_maven.c
/* program the MAVEN in monitor mode */

/* Authors:
   Mark Watson 6/2000,
   Rudolf Cornelissen 1/2003-5/2006

   Thanx to Petr Vandrovec for writing matroxfb.
*/

#define MODULE_BIT 0x00001000

#include "mga_std.h"

status_t g450_g550_maven_set_vid_pll(display_mode target);
status_t g100_g400max_maven_set_vid_pll(display_mode target);

/* This routine blanks the first 'line' below the screen used if there's memory left
 * to place it in. This will prevent overscanning rubbish on the MAVEN DAC, but only
 * if no screen using virtual height is setup.
 * In the rare event the mode and overlay use up so much RAM there's not enough room
 * left for one blank line, you will get overscanning rubbish from the overlay as it
 * will overwrite the blank line placed here.
 * This 'rectangle fill' is done in software because not all modes are supported by
 * the acc engine.
 * This function exists to partly overcome a G400 MAVEN hardware design fault, which
 * influences output in both monitor and TVout modes. The fault is that the MAVEN
 * keeps repeatedly displaying the last 8 pixels fetched from memory until fetching
 * restarts after a vertical retrace.
 * In order to let the maven fetch black pixels, an extra line has to be added to its
 * CRTC timing. This line needs to be entirely filled black, so with zeros. This line
 * will be displayed as last visible line, and the last 8 pixels of it will be
 * repeated during vertical retrace.
 * Note that the overscanning rubbish can be suppressed in TVout modes by extending
 * the vertical sync pulse all the way 'to the end'. */
status_t gx00_maven_clrline()
{
        uint32 x, screensize, pointer_reservation;
        uint8* adr;

        /* this function is nolonger needed on G450/G550 cards */
        if (si->ps.card_type > G400MAX) return B_OK;

        /* checkout space needed for hardcursor (if any) */
        pointer_reservation = 0;
        /* MIL 1/2 cards have a seperate buffer for the cursorbitmap inside the DAC */
        if ((si->ps.card_type >= G100) && si->settings.hardcursor) pointer_reservation = 1024;

        /* calculate actual screensize */
        screensize = si->fbc.bytes_per_row * si->dm.virtual_height;

        /* check if there's room left for a full blank line following the actual screen */
        if ((screensize + si->fbc.bytes_per_row + pointer_reservation) <=
                (si->ps.memory_size * 1024 * 1024))
        {
                LOG(4,("MAVEN: clearing line directly below screen\n"));

                /* calculate first adress beyond actual screen */
                adr = (uint8*)si->fbc.frame_buffer;
                adr += screensize;
                /* clear the line */
                for (x = 0; x < si->fbc.bytes_per_row; x++)
                {
                        *(adr + x) = 0x00;
                }
        }
        else
                LOG(4,("MAVEN: not clearing line directly below screen: no memory left\n"));

        return B_OK;
}

status_t gx00_maven_dpms(bool display, bool h, bool v)
{
        /* this function is nolonger needed on G450/G550 cards */
        if (si->ps.card_type > G400MAX) return B_OK;

        if (display && h && v)
        {
                /* turn on screen */
                if (!(si->dm.flags & TV_BITS))
                {
                        /* monitor mode */
                        MAVW(MONEN, 0xb2);
                        MAVW(MONSET, 0x20);             /* must be set to this in monitor mode */
                        MAVW(OUTMODE, 0x03);    /* output: monitor mode */
                        MAVW(STABLE, 0x22);             /* makes picture stable? */
                        MAVW(TEST, 0x00);               /* turn off test signal */
                }
                else
                {
                        /* TVout mode */
                        MAVW(MONEN, 0xb3);
                        MAVW(MONSET, 0x20);
                        MAVW(OUTMODE, 0x08);    /* output: SVideo/Composite */
                        MAVW(STABLE, 0x02);             /* makes picture stable? */
                        //fixme? linux uses 0x14...
                        MAVW(TEST, (MAVR(TEST) & 0x10));
                }
        }
        else
        {
                /* turn off screen using a few methods! */
                MAVW(STABLE, 0x6a);
                MAVW(OUTMODE, 0x00);
        }

        return B_OK;
}

/*set a mode line - inputs are in pixels/scanlines*/
status_t gx00_maven_set_timing(display_mode target)
{
        uint8 temp, cnt, offset, loop;

        /* this function is nolonger needed on G450/G550 cards */
        if (si->ps.card_type > G400MAX) return B_OK;

        LOG(4,("MAVEN: setting timing\n"));

        /*check horizontal timing parameters are to nearest 8 pixels*/
        if ((target.timing.h_display & 0x07)    |
                (target.timing.h_sync_start & 0x07)     |
                (target.timing.h_sync_end & 0x07)       |
                (target.timing.h_total & 0x07))
        {
                LOG(8,("MAVEN: Horizontal timing is not multiples of 8 pixels\n"));
                return B_ERROR;
        }

        /*program the MAVEN*/
        MAVWW(LASTLINEL, target.timing.h_total); 
        MAVWW(HSYNCLENL, (target.timing.h_sync_end - target.timing.h_sync_start));
        MAVWW(HSYNCSTRL, (target.timing.h_total - target.timing.h_sync_start));
        MAVWW(HDISPLAYL, ((target.timing.h_total - target.timing.h_sync_start) +
                                           target.timing.h_display));
        MAVWW(HTOTALL, (target.timing.h_total + 1));

        MAVWW(VSYNCLENL, (target.timing.v_sync_end - target.timing.v_sync_start - 1));
        MAVWW(VSYNCSTRL, (target.timing.v_total - target.timing.v_sync_start));
        MAVWW(VDISPLAYL, (target.timing.v_total - 1));
        MAVWW(VTOTALL, (target.timing.v_total - 1));

        MAVWW(HVIDRSTL, (target.timing.h_total - si->crtc_delay));
        MAVWW(VVIDRSTL, (target.timing.v_total - 2));

        /* setup HSYNC & VSYNC polarity */
        LOG(2,("MAVEN: sync polarity: "));
        temp = MAVR(LUMA);

        /* find out which offset from the 'reset position' we need */
        switch (((target.timing.flags & B_POSITIVE_HSYNC) >> (29 - 0)) |
                        ((target.timing.flags & B_POSITIVE_VSYNC) >> (30 - 1)))
        {
        case 0:
                /* H neg, V neg */
                LOG(2,("H:neg V:neg\n"));
                offset = 2;
                break;
        case 1:
                /* H pos, V neg */
                LOG(2,("H:pos V:neg\n"));
                offset = 3;
                break;
        case 2:
                /* H neg, V pos */
                LOG(2,("H:neg V:pos\n"));
                offset = 1;
                break;
        case 3:
        default:
                /* H pos, V pos */
                LOG(2,("H:pos V:pos\n"));
                offset = 0;
                break;
        }
        /* calculate the number of steps we need to make from the current 'position' */
        cnt = 0;
        if ((offset - ((int)si->maven_syncpol_offset)) < 0) cnt = 4;
        cnt += offset - si->maven_syncpol_offset;
        /* note the offset from 'reset position' we will have now */
        si->maven_syncpol_offset = offset;

        /* program the maven: */
        /* make sure pulse bit is reset */
        temp &= ~0x20;
        MAVW(LUMA, temp);
        snooze(5);
        /* enable maven sync polarity programming */
        temp |= 0x10;
        MAVW(LUMA, temp);
        snooze(5);
        /* pulse sync programming bit until we have the polarities we want */
        for (loop = 0; loop < cnt; loop++)
        {
                MAVW(LUMA, (temp | 0x20));
                snooze(5);
                MAVW(LUMA, (temp & ~0x20));
                snooze(5);
        }
        /* disable maven sync polarity programming and reset pulse bit */
        MAVW(LUMA, (temp & ~0x30));

        return B_OK;
}

/*set the mode, brightness is a value from 0->2 (where 1 is equivalent to direct)*/
status_t gx00_maven_mode(int mode,float brightness)
{
        uint8 luma;

        /* this function is nolonger needed on G450/G550 cards */
        if (si->ps.card_type > G400MAX) return B_OK;

        /* set luma to a suitable value for brightness */
        /* assuming 0x0a is a sensible value */
        /* fixme:
         * it looks like b6 and/or b7 determine the luma: just two values possible. */
        /* NOTE: b4 and b5 have another function, don't set! (sync polarity programming) */
        luma = (uint8)(0x0a * brightness);
        if (luma > 0x0f) luma = 0x0f;

        MAVW(LUMA, luma);
        LOG(4,("MAVEN: LUMA setting - %x\n", luma));

        return B_OK;
}

void gx00_maven_shutoff()
{
        switch (si->ps.card_type)
        {
                case G100:
                case G200:
                //fixme: see if this works on G400 too:
                //case G400:
                //case G400MAX:
                        /* prevents distortions on CRTC1... */
                        MAVW(TEST, 0x03);
                        MAVW(MONEN, 0x00);
                        MAVW(MONSET, 0x00);
                        break;
                default:
                        break;
        }
}

status_t gx00_maven_set_vid_pll(display_mode target)
{
        switch (si->ps.card_type)
        {
                case G450:
                case G550:
                        return g450_g550_maven_set_vid_pll(target);
                        break;
                default:
                        return g100_g400max_maven_set_vid_pll(target);
                        break;
        }
        return B_ERROR;
}
        
status_t g450_g550_maven_set_vid_pll(display_mode target)
{
        uint8 m=0,n=0,p=0;
        uint time = 0;

        float pix_setting, req_pclk;
        status_t result;

        req_pclk = (target.timing.pixel_clock)/1000.0;
        LOG(4,("MAVEN: Setting VID PLL for pixelclock %f\n", req_pclk));

        result = g450_g550_maven_vid_pll_find(target,&pix_setting,&m,&n,&p, 1);
        if (result != B_OK)
        {
                return result;
        }

        /*reprogram (disable,select,wait for stability,enable)*/
        CR2W(CTL, (CR2R(CTL) | 0x08));                                  /* disable the VIDPLL */
        CR2W(CTL, (CR2R(CTL) | 0x06));                                  /* select the VIDPLL */
        DXIW(VIDPLLM,(m));                                                              /* set m value */
        DXIW(VIDPLLN,(n));                                                              /* set n value */
        DXIW(VIDPLLP,(p));                                                              /* set p value */

        /* Wait for the VIDPLL frequency to lock until timeout occurs */
        while((!(DXIR(VIDPLLSTAT) & 0x40)) & (time <= 2000))
        {
                time++;
                snooze(1);
        }

        if (time > 2000)
                LOG(2,("MAVEN: VID PLL frequency not locked!\n"));
        else
                LOG(2,("MAVEN: VID PLL frequency locked\n"));
        CR2W(CTL, (CR2R(CTL) & ~0x08));                                 /* enable the VIDPLL */

        return B_OK;
}

/* program the video PLL in the MAVEN */
status_t g100_g400max_maven_set_vid_pll(display_mode target)
{
        uint8 m=0,n=0,p=0;

        float pix_setting, req_pclk;
        status_t result;

        req_pclk = (target.timing.pixel_clock)/1000.0;
        LOG(4,("MAVEN: Setting VID PLL for pixelclock %f\n", req_pclk));

        result = g100_g400max_maven_vid_pll_find(target,&pix_setting,&m,&n,&p);
        if (result != B_OK)
        {
                return result;
        }

        /*reprogram (select,wait for stability)*/
        MAVW(PIXPLLM,(m));                                                              /* set m value */
        MAVW(PIXPLLN,(n));                                                              /* set n value */
        MAVW(PIXPLLP,(p | 0x80));                                               /* set p value enabling PLL */

        /* Wait for the VIDPLL frequency to lock: detection is not possible it seems */
        snooze(2000);

        LOG(2,("MAVEN: VID PLL frequency should be locked now...\n"));

        return B_OK;
}


/* find nearest valid video PLL setting */
status_t g100_g400max_maven_vid_pll_find(
        display_mode target, float* calc_pclk, uint8* m_result, uint8* n_result,
        uint8* p_result)
{
        int m = 0, n = 0, p = 0, m_max;
        float error, error_best = INT_MAX;
        int best[3] = {0, 0, 0};
        float f_vco, max_pclk;
        float req_pclk = target.timing.pixel_clock/1000.0;

        /* determine the max. reference-frequency postscaler setting for the current card */
        //fixme: check G100 and G200 m_max if possible...
        switch (si->ps.card_type)
        {
        case G100:
                LOG(4, ("MAVEN: G100 restrictions apply\n"));
                m_max = 32;
                break;
        case G200:
                LOG(4, ("MAVEN: G200 restrictions apply\n"));
                m_max = 32;
                break;
        default:
                LOG(4, ("MAVEN: G400/G400MAX restrictions apply\n"));
                m_max = 32;
                break;
        }

        /* determine the max. pixelclock for the current videomode */
        switch (target.space)
        {
                case B_RGB16_LITTLE:
                        max_pclk = si->ps.max_dac2_clock_16;
                        break;
                case B_RGB32_LITTLE:
                        max_pclk = si->ps.max_dac2_clock_32;
                        break;
                default:
                        /* use fail-safe value */
                        max_pclk = si->ps.max_dac2_clock_32;
                        break;
        }
        /* if some dualhead mode is active, an extra restriction might apply */
        if ((target.flags & DUALHEAD_BITS) && (target.space == B_RGB32_LITTLE))
                max_pclk = si->ps.max_dac2_clock_32dh;

        /* Make sure the requested pixelclock is within the PLL's operational limits */
        /* lower limit is min_video_vco divided by highest postscaler-factor */
        if (req_pclk < (si->ps.min_video_vco / 8.0))
        {
                LOG(4, ("MAVEN: clamping vidclock: requested %fMHz, set to %fMHz\n",
                        req_pclk, (float)(si->ps.min_video_vco / 8.0)));
                req_pclk = (si->ps.min_video_vco / 8.0);
        }
        /* upper limit is given by pins in combination with current active mode */
        if (req_pclk > max_pclk)
        {
                LOG(4, ("MAVEN: clamping vidclock: requested %fMHz, set to %fMHz\n",
                        req_pclk, (float)max_pclk));
                req_pclk = max_pclk;
        }

        /* iterate through all valid PLL postscaler settings */
        for (p=0x01; p < 0x10; p = p<<1)
        {
                /* calculate the needed VCO frequency for this postscaler setting */
                f_vco = req_pclk * p;

                /* check if this is within range of the VCO specs */
                if ((f_vco >= si->ps.min_video_vco) && (f_vco <= si->ps.max_video_vco))
                {
                        /* iterate trough all valid reference-frequency postscaler settings */
                        for (m = 2; m <= m_max; m++)
                        {
                                /* calculate VCO postscaler setting for current setup.. */
                                n = (int)(((f_vco * m) / si->ps.f_ref) + 0.5);
                                /* ..and check for validity */
                                if ((n < 8) || (n > 128))       continue;

                                /* find error in frequency this setting gives */
                                error = fabs(req_pclk - (((si->ps.f_ref / m) * n) / p));

                                /* note the setting if best yet */
                                if (error < error_best)
                                {
                                        error_best = error;
                                        best[0]=m;
                                        best[1]=n;
                                        best[2]=p;
                                }
                        }
                }
        }

        /* setup the scalers programming values for found optimum setting */
        m=best[0] - 1;
        n=best[1] - 1;
        p=best[2] - 1;

        /* calc the needed PLL loopbackfilter setting belonging to current VCO speed */
        f_vco = (si->ps.f_ref / (m + 1)) * (n + 1);
        LOG(2, ("MAVEN: vid VCO frequency found %fMhz\n", f_vco));

        switch (si->ps.card_type)
        {
        case G100:
        case G200:
                for (;;)
                {
                        if (f_vco >= 180) {p |= (0x03 << 3); break;};
                        if (f_vco >= 140) {p |= (0x02 << 3); break;};
                        if (f_vco >= 100) {p |= (0x01 << 3); break;};
                        break;
                }
                break;
        default:
                for (;;)
                {
                        if (f_vco >= 240) {p |= (0x03 << 3); break;};
                        if (f_vco >= 170) {p |= (0x02 << 3); break;};
                        if (f_vco >= 110) {p |= (0x01 << 3); break;};
                        break;
                }
                break;
        }

        /* return the results */
        *calc_pclk = f_vco / ((p & 0x07) + 1);
        *m_result = m;
        *n_result = n;
        *p_result = p;

        /* display the found pixelclock values */
        LOG(2, ("MAVEN: vid PLL check: req. %fMHz got %fMHz, mnp 0x%02x 0x%02x 0x%02x\n",
                req_pclk, *calc_pclk, *m_result, *n_result, *p_result));

        return B_OK;
}


static status_t gx50_maven_check_vid_pll(uint8 m, uint8 n, uint8 p)
{
        uint time = 0, count = 0;

        /* reprogram (disable,select,wait for stability,enable) */
        CR2W(CTL, (CR2R(CTL) | 0x06));                                  /* select the VIDPLL */
        DXIW(VIDPLLM,(m));                                                              /* set m value */
        DXIW(VIDPLLN,(n));                                                              /* set n value */
        DXIW(VIDPLLP,(p));                                                              /* set p value */

        /* give the PLL 1mS at least to get a lock */
        time = 0;
        while((!(DXIR(VIDPLLSTAT) & 0x40)) & (time <= 1000))
        {
                time++;
                snooze(1);
        }
        
        /* no lock aquired, not useable */
        if (time > 1000) return B_ERROR;

        /* check if lock holds for at least 90% of the time */
        for (time = 0, count = 0; time <= 1000; time++)
        {
                if(DXIR(VIDPLLSTAT) & 0x40) count++;
                snooze(1);              
        }
        /* we have a winner */
        if (count >= 900) return B_OK;

        /* nogo, the PLL does not stabilize */
        return B_ERROR;
}

static status_t gx50_maven_check_vid_pll_range(uint8 m, uint8 n, uint8 *p, uint8 *q)
{
        uint8 s=0, p_backup = *p;

        /* preset no candidate, non working setting */
        *q = 0;
        /* preset lowest range filter */
        *p &= 0x47;

        /* iterate through all possible filtersettings */
        CR2W(CTL, (CR2R(CTL) | 0x08)); /* disable the VIDPLL */

        for (s = 0; s < 8 ;s++)
        {
                if (gx50_maven_check_vid_pll(m, n, *p)== B_OK)
                {
                        /* now check 3 closest lower and higher settings */
                        if ((gx50_maven_check_vid_pll(m, n - 3, *p)== B_OK) &&
                                (gx50_maven_check_vid_pll(m, n - 2, *p)== B_OK) &&
                                (gx50_maven_check_vid_pll(m, n - 1, *p)== B_OK) &&
                                (gx50_maven_check_vid_pll(m, n + 1, *p)== B_OK) &&
                                (gx50_maven_check_vid_pll(m, n + 2, *p)== B_OK) &&
                                (gx50_maven_check_vid_pll(m, n + 3, *p)== B_OK))
                        {
                                LOG(2,("MAVEN: found optimal working VCO filter: #%d\n",s));
                                /* preset first choice setting found */
                                *q = 1;
                                /* we are done */
                                CR2W(CTL, (CR2R(CTL) & ~0x08)); /* enable the VIDPLL */
                                return B_OK;
                        }
                        else
                        {
                                LOG(2,("MAVEN: found critical but working VCO filter: #%d\n",s));
                                /* preset backup setting found */
                                *q = 2;
                                /* remember this setting */
                                p_backup = *p;
                                /* let's continue to see if a better filter exists */
                        }
                }
        /* new filtersetting to try */
        *p += (1 << 3);
        }

        /* return the (last found) backup result, or the original p value */
        *p = p_backup;  
        CR2W(CTL, (CR2R(CTL) & ~0x08)); /* enable the VIDPLL */
        /* we found only a non-optimal value */
        if (*q == 2) return B_OK;

        /* nothing worked at all */
        LOG(2,("MAVEN: no working VCO filter found!\n"));
        return B_ERROR;
}


/* find nearest valid video PLL setting */
status_t g450_g550_maven_vid_pll_find
        (display_mode target, float* calc_pclk, uint8* m_result, uint8* n_result,
        uint8* p_result, uint8 test)
{
        int m = 0, n = 0;
        uint8 p = 0, q = 0;
        float error, error_best = INFINITY;
        int best[3] = {0, 0, 0};
        float f_vco, max_pclk;
        float req_pclk = target.timing.pixel_clock/1000.0;

        LOG(4, ("MAVEN: G450/G550 restrictions apply\n"));

        /* determine the max. pixelclock for the current videomode */
        switch (target.space)
        {
                case B_RGB16_LITTLE:
                        max_pclk = si->ps.max_dac2_clock_16;
                        break;
                case B_RGB32_LITTLE:
                        max_pclk = si->ps.max_dac2_clock_32;
                        break;
                default:
                        /* use fail-safe value */
                        max_pclk = si->ps.max_dac2_clock_32;
                        break;
        }
        /* if some dualhead mode is active, an extra restriction might apply */
        if ((target.flags & DUALHEAD_BITS) && (target.space == B_RGB32_LITTLE))
                max_pclk = si->ps.max_dac2_clock_32dh;

        /* Make sure the requested pixelclock is within the PLL's operational limits */
        /* lower limit is min_pixel_vco divided by highest postscaler-factor */
        if (req_pclk < (si->ps.min_video_vco / 16.0))
        {
                LOG(4, ("MAVEN: clamping vidclock: requested %fMHz, set to %fMHz\n",
                        req_pclk, (float)(si->ps.min_video_vco / 16.0)));
                req_pclk = (si->ps.min_video_vco / 16.0);
        }
        /* upper limit is given by pins in combination with current active mode */
        if (req_pclk > max_pclk)
        {
                LOG(4, ("MAVEN: clamping vidclock: requested %fMHz, set to %fMHz\n",
                        req_pclk, (float)max_pclk));
                req_pclk = max_pclk;
        }

        /* iterate through all valid PLL postscaler settings */
        for (p=0x01; p < 0x20; p = p<<1)
        {
                /* calculate the needed VCO frequency for this postscaler setting */
                f_vco = req_pclk * p;

                /* check if this is within range of the VCO specs */
                if ((f_vco >= si->ps.min_video_vco) && (f_vco <= si->ps.max_video_vco))
                {
                        /* iterate trough all valid reference-frequency postscaler settings */
                        for (m = 2; m <= 32; m++)
                        {
                                /* calculate VCO postscaler setting for current setup.. */
                                n = (int)(((f_vco * m) / (si->ps.f_ref * 2)) + 0.5);
                                /* ..and check for validity, BUT:
                                 * Keep in mind that we need to be able to test n-3 ... n+3! */
                                if ((n < (8 + 3)) || (n > (128 - 3)))   continue;

                                /* find error in frequency this setting gives */
                                error = fabs(req_pclk - ((((si->ps.f_ref * 2)/ m) * n) / p));

                                /* note the setting if best yet */
                                if (error < error_best)
                                {
                                        error_best = error;
                                        best[0]=m;
                                        best[1]=n;
                                        best[2]=p;
                                }
                        }
                }
        }

        /* setup the scalers programming values for found optimum setting */
        m=best[0] - 1;
        n=best[1] - 2;
        switch (best[2])
        {
        case 1:
                p = 0x40;
                break;
        case 2:
                p = 0x00;
                break;
        case 4:
                p = 0x01;
                break;
        case 8:
                p = 0x02;
                break;
        case 16:
                p = 0x03;
                break;
        }

        /* log the closest VCO speed found */
        f_vco = ((si->ps.f_ref * 2) / (m + 1)) * (n + 2);
        LOG(2, ("MAVEN: vid VCO frequency found %fMhz\n", f_vco));

        /* now find the filtersetting that matches best with this frequency by testing.
         * for now we assume this routine succeeds to get us a stable setting */
        if (test)
                gx50_maven_check_vid_pll_range(m, n, &p, &q);
        else
                LOG(2, ("MAVEN: Not testing G450/G550 VCO feedback filters\n"));

        /* return the results */
        *calc_pclk = f_vco / best[2];
        *m_result = m;
        *n_result = n;
        *p_result = p;

        /* display the found pixelclock values */
        LOG(2, ("MAVEN: vid PLL check: req. %fMHz got %fMHz, mnp 0x%02x 0x%02x 0x%02x\n",
                req_pclk, *calc_pclk, *m_result, *n_result, *p_result));

        return B_OK;
}