|
|
/*++
Copyright (c) 1993, 1994 Weitek Corporation Module Name: clock.c Abstract: This module contains clock generator specific functions for the Weitek P9 miniport device driver. Environment: Kernel mode Revision History may be found at the end of this file.
--*/
#include "p9.h"
#include "p9gbl.h"
#include "clock.h"
#include "vga.h"
#include "ibm525.h"
#include "p91regs.h"
extern UCHAR ReadIBM525( PHW_DEVICE_EXTENSION HwDeviceExtension, USHORT index );
extern VOID WriteIBM525( PHW_DEVICE_EXTENSION HwDeviceExtension, USHORT index, UCHAR value );
extern UCHAR ReadP9ConfigRegister( PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR regnum );
extern VOID WriteP9ConfigRegister( PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR regnum, UCHAR jValue );
extern ULONG Read9100FreqSel( PHW_DEVICE_EXTENSION HwDeviceExtension );
VOID Write525PLL( PHW_DEVICE_EXTENSION HwDeviceExtension, USHORT usFreq );
VOID Write9100FreqSel( PHW_DEVICE_EXTENSION HwDeviceExtension, ULONG cs );
VOID P91WriteICD( PHW_DEVICE_EXTENSION HwDeviceExtension, ULONG data );
VOID P90WriteICD( PHW_DEVICE_EXTENSION HwDeviceExtension, ULONG data );
VOID ProgramClockSynth( PHW_DEVICE_EXTENSION HwDeviceExtension, USHORT usFrequency, BOOLEAN bSetMemclk, BOOLEAN bUseClockDoubler )
/*++
Routine Description:
Program a custom frequency into a clock synthesizer. Either MEMCLK or Pixel clock, determined by bSetMemclk.
Arguments:
HwDeviceExtension - Pointer to the miniport driver's device extension. usFrequency = Frequency in Mhz, (this shoud be kept in the registry), bSetMemclk == TRUE == MEMCLK.
Return Value:
None.
--*/
{
VideoDebugPrint((2, "ProgramClockSynth - Entry\n"));
switch (HwDeviceExtension->p91State.usClockID) { case CLK_ID_ICD2061A:
VideoDebugPrint((2, "ProgramClockSynth: Clock = CLK_ID_ICD2061A\n"));
if ((HwDeviceExtension->Dac.bRamdacUsePLL) && (HwDeviceExtension->Dac.usRamdacID == DAC_ID_IBM525) && (!bSetMemclk)) {
VideoDebugPrint((2, "ProgramClockSynth: DAC_ID_IBM525\n")); VideoDebugPrint((2, "ProgramClockSynth: PLL Freq = %d\n", usFrequency)); Write525PLL(HwDeviceExtension, usFrequency);
//
// Check if there is an override value for the PLL Reference
// Divider. If so, set the reference frequency accordingly...
//
if (HwDeviceExtension->VideoData.ul525RefClkCnt != 0xFFFFFFFF) { VideoDebugPrint((2, "ProgramClockSynth: 525RefClkCnt = %ld\n", HwDeviceExtension->VideoData.ul525RefClkCnt * 200L));
usFrequency = (USHORT) (HwDeviceExtension->VideoData.ul525RefClkCnt * 200L); } else { //
// Set reference frequency to 5000 Mhz...
//
usFrequency = 5000; } VideoDebugPrint((2, "ProgramClockSynth: 525 Ref Frequency = %d\n", usFrequency)); }
DevSetClock(HwDeviceExtension, usFrequency, bSetMemclk, bUseClockDoubler);
//
// Select custom frequency
//
Write9100FreqSel(HwDeviceExtension, ICD2061_EXTSEL9100);
break;
case CLK_ID_FIXED_MEMCLK:
//
// People using the IBM RGB525 RAMDAC with a fixed
// external oscillator will be using the RGB525's internal
// PLL. So the MEMCLK cannot be changed, and the pixel
// clock must be programmed through the RAMDAC.
// NOTE: This code will work even if the RAMDAC's
// internal PLL is bypassed and an external oscillator
// is used.
//
// NOTE: We don't print out any kind of error message
// if an attempt is made to program the memory clock
// frequency.
//
// We assume that the reference frequency is 50 MHz.
//
VideoDebugPrint((2, "ProgramClockSynth: Clock = CLK_ID_FIXED_MEMCLKC\n"));
if ((HwDeviceExtension->Dac.usRamdacID == DAC_ID_IBM525) && (!bSetMemclk)) { Write525PLL(HwDeviceExtension, usFrequency); }
if ((HwDeviceExtension->Dac.bRamdacUsePLL) && (HwDeviceExtension->Dac.usRamdacID == DAC_ID_IBM525) && (!bSetMemclk)) { VideoDebugPrint((2, "ERROR: Trying to select a pixclk with RGB525 disabled.\n")); }
break; }
VideoDebugPrint((2, "ProgramClockSynth - Exit\n"));
} // End of ProgramClockSynth()
VOID DevSetClock( PHW_DEVICE_EXTENSION HwDeviceExtension, USHORT usFrequency, BOOLEAN bSetMemclk, BOOLEAN bUseClockDoubler )
/*++
Routine Description:
Set the frequency synthesizer to the proper state for the current video mode.
Arguments:
usFrequency == frequency. bUseClockDoubler == TRUE == use clock doubler if appropriate.
Return Value:
None.
--*/
{ USHORT ftab[16]= { 5100,5320,5850,6070,6440,6680,7350,7560,8090, 8320,9150,10000,12000,12000,12000,12000 };
USHORT ref = 5727; // reference freq 2*14.31818 *100*2
int i = 0; // index preset field
int m = 0; // power of 2 divisor field
int p; // multiplier field
int q; // divisor field
int qs; // starting q to prevent integer overflow
int bestq = 0; // best q so far
int bestp = 0; // best p so far
int bestd = 10000; // distance to best combination so far
int curd; // current distance
int curf; // current frequency
int j; // loop counter
ULONG data;
VideoDebugPrint((2, "DevSetClock - Entry\n"));
if (usFrequency == 0) // Prevent 0 from hanging us!
usFrequency = 3150;
if ((usFrequency > HwDeviceExtension->Dac.ulMaxClkFreq) && (bUseClockDoubler) && (HwDeviceExtension->Dac.usRamdacID != DAC_ID_IBM525)) { //
// Enable the DAC clock doubler mode.
//
HwDeviceExtension->Dac.DACSetClkDblMode(HwDeviceExtension); // 2x Clock multiplier enabled
usFrequency /= 2; // Use 1/2 the freq.
} else { //
// Disable the DAC clock doubler mode.
//
HwDeviceExtension->Dac.DACClrClkDblMode(HwDeviceExtension); }
while(usFrequency < 5000) // if they need a small frequency,
{ m += 1; // the hardware can divide by 2 to-the m
usFrequency *= 2; // so try for a higher frequency
}
for (j = 0; j < 16; j++) // find the range that best fits this frequency
{ if (usFrequency < ftab[j]) // when you find the frequency
{ i = j; // remember the table index
break; // and stop looking.
} }
for (p = 0; p < 128; p++) // try all possible frequencies!
{ //well, start q high to avoid overflow
qs = div32(mul32((SHORT) ref, (SHORT) (p+3)), 0x7fff);
for (q = qs; q < 128; q++) { //
// calculate how good each frequency is
//
curf = div32(mul32((SHORT) ref, (SHORT) (p+3)), (SHORT) ((q + 2) << 1)); curd = usFrequency - curf;
if (curd < 0) { curd = -curd; // always calc a positive distance
}
if (curd < bestd) // if it's best of all so far
{ bestd = curd; // then remember everything about it
bestp = p; // but especially the multiplier
bestq = q; // and divisor
} } }
data = ((((long) i) << 17) | (((long) bestp) << 10) | (m << 7) | bestq);
VideoDebugPrint((2, "ProgramClockSynth: usFrequency = %d\n", usFrequency)); VideoDebugPrint((2, "ProgramClockSynth: data = %lx\n", data));
if (bSetMemclk) { data = data | IC_MREG; // Memclk
} else { data = data | IC_REG2; // Pixclk
}
if (IS_DEV_P9100) { P91WriteICD(HwDeviceExtension, data); } else { P90WriteICD(HwDeviceExtension, data); }
VideoDebugPrint((2, "DevSetClock - Exit\n"));
return;
} // End of DevSetClock()
VOID P91WriteICD( PHW_DEVICE_EXTENSION HwDeviceExtension, ULONG data )
/*++
Routine Description:
Program the ICD2061a Frequency Synthesizer.
Arguments:
HwDeviceExtension - Pointer to the miniport driver's device extension. data - Data to be written.
Return Value:
None.
--*/
{ int i; ULONG savestate;
//
// Note: We might have to disable interrupts to preclude the ICD's
// watchdog timer from expiring resulting in the ICD resetting to the
// idle state.
//
savestate = Read9100FreqSel(HwDeviceExtension);
//
// First, send the "Unlock sequence" to the clock chip.
// Raise the data bit and send 5 unlock bits.
//
Write9100FreqSel(HwDeviceExtension, ICD2061_DATA9100); for (i = 0; i < 5; i++) // send at least 5 unlock bits
{ //
// Hold the data while lowering and raising the clock
//
Write9100FreqSel(HwDeviceExtension, ICD2061_DATA9100); Write9100FreqSel(HwDeviceExtension, ICD2061_DATA9100 | ICD2061_CLOCK9100); }
//
// Then turn the data clock off and turn the clock on one more time...
//
Write9100FreqSel(HwDeviceExtension, 0); Write9100FreqSel(HwDeviceExtension, ICD2061_CLOCK9100);
//
// Now send the start bit: Leave data off, adn lower the clock.
//
Write9100FreqSel(HwDeviceExtension, 0);
//
// Leave data off and raise the clock.
//
Write9100FreqSel(HwDeviceExtension, ICD2061_CLOCK9100);
//
// Localbus position for hacking bits out
// Next, send the 24 data bits.
//
for (i = 0; i < 24; i++) { //
// Leaving the clock high, raise the inverse of the data bit
//
Write9100FreqSel(HwDeviceExtension, ((~data << ICD2061_DATASHIFT9100) & ICD2061_DATA9100) | ICD2061_CLOCK9100);
//
// Leaving the inverse data in place, lower the clock.
//
Write9100FreqSel(HwDeviceExtension, (~data << ICD2061_DATASHIFT9100) & ICD2061_DATA9100);
//
// Leaving the clock low, rais the data bit.
//
Write9100FreqSel(HwDeviceExtension, (data << ICD2061_DATASHIFT9100) & ICD2061_DATA9100);
//
// Leaving the data bit in place, raise the clock.
//
Write9100FreqSel(HwDeviceExtension, ((data << ICD2061_DATASHIFT9100) & ICD2061_DATA9100) | ICD2061_CLOCK9100);
data >>= 1; // get the next bit of the data
}
//
// Leaving the clock high, raise the data bit.
//
Write9100FreqSel(HwDeviceExtension, ICD2061_CLOCK9100 | ICD2061_DATA9100);
//
// Leaving the data high, drop the clock low, then high again.
//
Write9100FreqSel(HwDeviceExtension, ICD2061_DATA9100); Write9100FreqSel(HwDeviceExtension, ICD2061_CLOCK9100 | ICD2061_DATA9100); //
// Note: if interrupts were disabled, enable them here.
// before restoring the
// original value or the ICD
// will freak out.
Write9100FreqSel(HwDeviceExtension, savestate); // restore orig register value
return;
} // End of WriteICD()
VOID P90WriteICD( PHW_DEVICE_EXTENSION HwDeviceExtension, ULONG data )
/*++
Routine Description:
Program the ICD2061a Frequency Synthesizer for the Power 9000.
Arguments:
HwDeviceExtension - Pointer to the miniport driver's device extension. data - Data to be written.
Return Value:
None.
--*/
{ int i; int oldstate, savestate;
savestate = RD_ICD(); oldstate = savestate & ~(MISCD | MISCC);
// First, send the "Unlock sequence" to the clock chip.
WR_ICD(oldstate | MISCD); // raise the data bit
for (i = 0;i < 5;i++) // send at least 5 unlock bits
{ WR_ICD(oldstate | MISCD); // hold the data on while
WR_ICD(oldstate | MISCD | MISCC); // lowering and raising the clock
}
WR_ICD(oldstate); // then turn the data and clock off
WR_ICD(oldstate | MISCC); // and turn the clock on one more time.
// now send the start bit:
WR_ICD(oldstate); // leave data off, and lower the clock
WR_ICD(oldstate | MISCC); // leave data off, and raise the clock
// localbus position for hacking bits out
// Next, send the 24 data bits.
for (i = 0; i < 24; i++) { // leaving the clock high, raise the inverse of the data bit
WR_ICD(oldstate | ((~(((short) data) << 3)) & MISCD) | MISCC);
// leaving the inverse data in place, lower the clock
WR_ICD(oldstate | (~(((short) data) << 3)) & MISCD);
// leaving the clock low, rais the data bit
WR_ICD(oldstate | (((short) data) << 3) & MISCD);
// leaving the data bit in place, raise the clock
WR_ICD(oldstate | ((((short)data) << 3) & MISCD) | MISCC);
data >>= 1; // get the next bit of the data
}
// leaving the clock high, raise the data bit
WR_ICD(oldstate | MISCD | MISCC);
// leaving the data high, drop the clock low, then high again
WR_ICD(oldstate | MISCD); WR_ICD(oldstate | MISCD | MISCC); WR_ICD(oldstate | MISCD | MISCC); // Seem to need a delay
// before restoring the
// original value or the ICD
// will freak out.
WR_ICD(savestate); // restore original register value
return;
} // End of P90WriteICD()
VOID Write9100FreqSel( PHW_DEVICE_EXTENSION HwDeviceExtension, ULONG cs ) /*++
Routine Description:
Write to the P9100 clock select register preserving the video coprocessor enable bit.
Statically: Bits [1:0] go to frequency select Dynamically: Bit 1: data Bit 0: clock
Arguments:
HwDeviceExtension - Pointer to the miniport driver's device extension. Clock select value to write.
Return Value:
None.
--*/
{ //
// Set the frequency select bits in the P9100 configuration
//
WriteP9ConfigRegister(HwDeviceExtension, P91_CONFIG_CKSEL, (UCHAR) ((cs << 2) | HwDeviceExtension->p91State.bVideoPowerEnabled)); return;
} // End of Write9100FreqSel()
ULONG Read9100FreqSel( PHW_DEVICE_EXTENSION HwDeviceExtension ) /*++
Routine Description:
Read from the P9100 clock select register.
Arguments:
HwDeviceExtension - Pointer to the miniport driver's device extension.
Return Value:
None.
--*/
{ //
// Since the frequency select bits are in the P9100 configuration
// space, you have to treat VL and PCI differently.
//
return((ULONG)(ReadP9ConfigRegister(HwDeviceExtension, P91_CONFIG_CKSEL) >> 2) & 0x03);
} // End of Read9100FreqSel()
VOID Write525PLL( PHW_DEVICE_EXTENSION HwDeviceExtension, USHORT usFreq )
/*++
Routine Description:
This function programs the IBM RGB525 Ramdac to generate and use the specified frequency as its pixel clock frequency.
Arguments:
Frequency.
Return Value:
None.
--*/
{ USHORT usDesiredFreq; USHORT usOutputFreq; USHORT usRoundedFreq; USHORT usVCODivCount; ULONG ulValue;
VideoDebugPrint((2, "Write525PLL------\n"));
usOutputFreq = usFreq;
//
// Calculate the DF and VCO Divide count for the specified output
// frequency. The calculations are based on the following table:
//
// DF | VCO Divide Count | Frequency Range | Step (MHz)
// ----+-------------------+---------------------+------------
// 00 | (4 x VF) - 65 | 16.25 - 32.00 MHz | 0.25
// 01 | (2 x VF) - 65 | 32.50 - 64.00 MHz | 0.50
// 10 | VF - 65 | 65.00 - 128.00 MHz | 1.00
// 11 | (VF / 2) - 65 | 130.00 - 250.00 MHz | 2.00
// -----------------------------------------------------------
// VF = Desired Video Frequency
// -----------------------------------------------------------
//
if ((usOutputFreq >= IBM525_DF0_LOW) && (usOutputFreq <= IBM525_DF0_HIGH)) { //
// The requested frequency is in the DF0 frequency range.
//
usDesiredFreq = IBM525_FREQ_DF_0; //
// Round the requested frequency to the nearest frequency step
// boundry.
//
usRoundedFreq = (usOutputFreq / IBM525_DF0_STEP) * IBM525_DF0_STEP; if ((usOutputFreq - usRoundedFreq) >= (IBM525_DF0_STEP / 2)) { //
// Round up.
//
usRoundedFreq += IBM525_DF0_STEP; } //
// Calculate the VCO Divide Count register value for the requested
// frequency.
//
usVCODivCount = ((usRoundedFreq * 4) - 6500) / 100; } else if ((usOutputFreq >= IBM525_DF1_LOW) && (usOutputFreq <= IBM525_DF1_HIGH)) { //
// The requested frequency is in the DF1 frequency range.
//
usDesiredFreq = IBM525_FREQ_DF_1; //
// Round the requested frequency to the nearest frequency step
// boundry.
//
usRoundedFreq = (usOutputFreq / IBM525_DF1_STEP) * IBM525_DF1_STEP; if ((usOutputFreq - usRoundedFreq) >= (IBM525_DF1_STEP / 2)) { //
// Round up.
//
usRoundedFreq += IBM525_DF1_STEP; } //
// Calculate the VCO Divide Count register value for the requested
// frequency.
//
usVCODivCount = ((usRoundedFreq * 2) - 6500) / 100; } else if ((usOutputFreq >= IBM525_DF2_LOW) && (usOutputFreq <= IBM525_DF2_HIGH)) { //
// The requested frequency is in the DF2 frequency range.
//
usDesiredFreq = IBM525_FREQ_DF_2; //
// Round the requested frequency to the nearest frequency step
// boundry.
//
usRoundedFreq = (usOutputFreq / IBM525_DF2_STEP) * IBM525_DF2_STEP; if ((usOutputFreq - usRoundedFreq) >= (IBM525_DF2_STEP / 2)) { //
// Round up.
//
usRoundedFreq += IBM525_DF2_STEP; } //
// Calculate the VCO Divide Count register value for the requested
// frequency.
//
usVCODivCount = (usRoundedFreq - 6500) / 100; } else if ((usOutputFreq >= IBM525_DF3_LOW) && (usOutputFreq <= IBM525_DF3_HIGH)) { //
// The requested frequency is in the DF3 frequency range.
//
usDesiredFreq = IBM525_FREQ_DF_3; //
// Round the requested frequency to the nearest frequency step
// boundry.
//
usRoundedFreq = (usOutputFreq / IBM525_DF3_STEP) * IBM525_DF3_STEP; if ((usOutputFreq - usRoundedFreq) >= (IBM525_DF3_STEP / 2)) { //
// Round up.
//
usRoundedFreq += IBM525_DF3_STEP; } //
// Calculate the VCO Divide Count register value for the requested
// frequency.
//
usVCODivCount = ((usRoundedFreq / 2) - 6500) / 100; } else { //
// The requested frequency is not supported...
//
VideoDebugPrint((2, "Write525PLL: Freq %d is not supported!\n", usFreq)); } VideoDebugPrint((2, "Write525PLL: usRoundedFreq = %d\n", usRoundedFreq)); VideoDebugPrint((2, "Write525PLL: usVCODivCount = %d\n", usVCODivCount)); //
// Setup for writing to the PLL Reference Divider register.
//
//
// Check if there is an override value for the PLL Reference Divider.
//
if (HwDeviceExtension->VideoData.ul525RefClkCnt != 0xFFFFFFFF) { //
// Program REFCLK to the specified override...
//
WriteIBM525(HwDeviceExtension, RGB525_FIXED_PLL_REF_DIV, (UCHAR) HwDeviceExtension->VideoData.ul525RefClkCnt); VideoDebugPrint((2, "ProgramClockSynth: 525RefClkCnt = %lx\n", HwDeviceExtension->VideoData.ul525RefClkCnt)); } else { //
// Program REFCLK to a fixed 50MHz.
//
WriteIBM525(HwDeviceExtension, RGB525_FIXED_PLL_REF_DIV, IBM525_PLLD_50MHZ); }
//
// Set up for programming frequency register 9.
//
//
// Check if there is an override value for the VCO Divide Count Register.
//
if (HwDeviceExtension->VideoData.ul525VidClkFreq != 0xFFFFFFFF) { //
// Program the VCO Divide count register to the specified override...
//
WriteIBM525(HwDeviceExtension, RGB525_F9, (UCHAR) HwDeviceExtension->VideoData.ul525VidClkFreq); } else { //
// No override value, so use the calculated value.
//
WriteIBM525(HwDeviceExtension, RGB525_F9, (UCHAR) (usDesiredFreq | usVCODivCount)); }
//
// Program PLL Control Register 2.
//
WriteIBM525(HwDeviceExtension, RGB525_PLL_CTL2, IBM525_PLL2_F9_REG); //
// Program PLL Control Register 1.
//
WriteIBM525(HwDeviceExtension, RGB525_PLL_CTL1, (IBM525_PLL1_REFCLK_INPUT | IBM525_PLL1_INT_FS) ); //
// Program DAC Operation Register.
//
WriteIBM525(HwDeviceExtension, RGB525_DAC_OPER, IBM525_DO_DSR_FAST); //
// Program Miscellaneous Control Register 1.
//
WriteIBM525(HwDeviceExtension, RGB525_MISC_CTL1, IBM525_MC1_VRAM_64_BITS); //
// Program Miscellaneous Clock Control Register.
//
ulValue = ReadIBM525(HwDeviceExtension, RGB525_MISC_CLOCK_CTL); //
// Now decide how to divide the PLL clock output.
//
if (!HwDeviceExtension->Dac.bRamdacDivides) { if (HwDeviceExtension->usBitsPixel == 24) { //
// 24 Bpp = 3 Byte Per Pixel
//
// At 24 Bpp, divide the clock by 8.
//
ulValue |= IBM525_MCC_PLL_DIV_8 | IBM525_MCC_PLL_ENABLE; } else { //
// Don't divide the clock when the P9100 is doing the dividing.
//
ulValue |= IBM525_MCC_PLL_DIV_1 | IBM525_MCC_PLL_ENABLE; } } else { switch (HwDeviceExtension->usBitsPixel) { //
// 8 Bpp = 1 Byte Per Pixel
//
case 8: { //
// At 8 Bpp, divide the clock by 8.
//
ulValue |= IBM525_MCC_PLL_DIV_8 | IBM525_MCC_PLL_ENABLE; break; } //
// 16 Bpp = 2 Byte Per Pixel
//
case 15: case 16: { //
// At 16 Bpp, divide the clock by 4.
//
ulValue |= IBM525_MCC_PLL_DIV_4 | IBM525_MCC_PLL_ENABLE; break; } //
// 24 Bpp = 3 Byte Per Pixel
//
case 24: { //
// At 24 Bpp, divide the clock by 8.
//
ulValue |= IBM525_MCC_PLL_DIV_8 | IBM525_MCC_PLL_ENABLE; break; } //
// 32 Bpp = 4 Byte Per Pixel
//
case 32: { //
// At 32 Bpp, divide the clock by 2.
//
ulValue |= IBM525_MCC_PLL_DIV_2 | IBM525_MCC_PLL_ENABLE; break; } } } WriteIBM525(HwDeviceExtension, RGB525_MISC_CLOCK_CTL, (UCHAR) ulValue); return;
} // End of Write525PLL()
|