windows-nt/Source/XPSP1/NT/drivers/video/ms/weitek/mini/p91clock.c

1008 lines
25 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
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()