/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.driver.video.ati.radeon;
import org.jnode.util.NumberUtils;
import org.jnode.util.TimeUtils;
/**
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
final class PllRegs implements RadeonConstants {
private final int architecture;
private final boolean crtc2;
private static final int[] DIVIDERS = {1, 2, 4, 8, 3, 16, 6, 12};
/* PLL regs */
private int ppll_div_3;
private int ppll_ref_div;
private int htotal_cntl;
private int vclk_ecp_cntl;
/**
*
* @param crtc2
*/
public PllRegs(int architecture, boolean crtc2) {
this.architecture = architecture;
this.crtc2 = crtc2;
}
/**
* Save the PLL registers.
*
* @param io
*/
public final void savePLL(RadeonVgaIO io) {
this.ppll_ref_div = io.getPLL(PPLL_REF_DIV);
this.ppll_div_3 = io.getPLL(PPLL_DIV_3);
this.htotal_cntl = io.getPLL(HTOTAL_CNTL);
this.vclk_ecp_cntl = io.getPLL(VCLK_ECP_CNTL);
dumpPLLRegs(io, "@@@ SavePLL");
}
/**
* Restore the PLL registers
*
* @param io
*/
public final void restorePLL(RadeonVgaIO io) {
// do {
// io.setRegP32(CLOCK_CNTL_INDEX, PPLL_DIV_SEL_MASK, 0xffff);
// } while ((io.getReg32(CLOCK_CNTL_INDEX) & PPLL_DIV_SEL_MASK) !=
// PPLL_DIV_SEL_MASK);
//
// io.setPLLP(PPLL_CNTL, PPLL_RESET, 0xffff);
//
// do {
// io.setPLLP(PPLL_REF_DIV, ppll_ref_div, ~PPLL_REF_DIV_MASK);
// } while ((io.getPLL(PPLL_REF_DIV) & PPLL_REF_DIV_MASK) !=
// (ppll_ref_div & PPLL_REF_DIV_MASK));
//
// do {
// io.setPLLP(PPLL_DIV_3, ppll_div_3, ~PPLL_FB3_DIV_MASK);
// } while ((io.getPLL(PPLL_DIV_3) & PPLL_FB3_DIV_MASK) !=
// (ppll_div_3 & PPLL_FB3_DIV_MASK));
//
// do {
// io.setPLLP(PPLL_DIV_3, ppll_div_3, ~PPLL_POST3_DIV_MASK);
// } while ((io.getPLL(PPLL_DIV_3) & PPLL_POST3_DIV_MASK) !=
// (ppll_div_3 & PPLL_POST3_DIV_MASK));
//
// io.setPLL(HTOTAL_CNTL, 0);
// io.setPLLP(PPLL_CNTL, 0, ~PPLL_RESET);
// Swich VCKL clock input to CPUCLK so it stays fed while PPLL updates
io.setPLLP(VCLK_ECP_CNTL, VCLK_SRC_CPU_CLK, ~VCLK_SRC_SEL_MASK);
// Reset PPLL & enable atomic update
io.setPLLP(PPLL_CNTL, PPLL_RESET | PPLL_ATOMIC_UPDATE_EN | PPLL_VGA_ATOMIC_UPDATE_EN,
~(PPLL_RESET | PPLL_ATOMIC_UPDATE_EN | PPLL_VGA_ATOMIC_UPDATE_EN));
/* Switch to PPLL div 3 */
io.setRegP32(CLOCK_CNTL_INDEX, PPLL_DIV_SEL_MASK, ~PPLL_DIV_SEL_MASK);
/* Set PPLL ref. div */
switch (architecture) {
default:
io.setPLLP(PPLL_REF_DIV, ppll_ref_div, ~PPLL_REF_DIV_MASK);
break;
case Architecture.R300:
throw new Error("Not implemented");
// if (ppll_ref_div & R300_PPLL_REF_DIV_ACC_MASK) {
// /* When restoring console mode, use saved PPLL_REF_DIV
// * setting.
// */
// io.setPLLP(PPLL_REF_DIV, ppll_ref_div, 0);
// } else {
// /* R300 uses ref_div_acc field as real ref divider */
// io.setPLLP(PPLL_REF_DIV,
// (ppll_ref_div << R300_PPLL_REF_DIV_ACC_SHIFT),
// ~R300_PPLL_REF_DIV_ACC_MASK);
// }
// break;
}
// Set PPLL divider 3 & post divider
io.setPLLP(PPLL_DIV_3, ppll_div_3, ~PPLL_FB3_DIV_MASK);
io.setPLLP(PPLL_DIV_3, ppll_div_3, ~PPLL_POST3_DIV_MASK);
/* Write update */
while ((io.getPLL(PPLL_REF_DIV) & PPLL_ATOMIC_UPDATE_R) != 0) {
// Wait
}
io.setPLLP(PPLL_REF_DIV, PPLL_ATOMIC_UPDATE_W, ~PPLL_ATOMIC_UPDATE_W);
/* Wait read update complete */
/*
* FIXME: Certain revisions of R300 can't recover here. Not sure of the
* cause yet, but this workaround will mask the problem for now. Other
* chips usually will pass at the very first test, so the workaround
* shouldn't have any effect on them.
*/
for (int i = 0; (i < 10000) && ((io.getPLL(PPLL_REF_DIV) & PPLL_ATOMIC_UPDATE_R) != 0); i++) {
// Wait
}
io.setPLL(HTOTAL_CNTL, 0);
/* Clear reset & atomic update */
io.setPLLP(PPLL_CNTL, 0,
~(PPLL_RESET | PPLL_SLEEP | PPLL_ATOMIC_UPDATE_EN | PPLL_VGA_ATOMIC_UPDATE_EN));
/* We may want some locking ... oh well */
TimeUtils.sleep(5);
/* Switch back VCLK source to PPLL */
io.setPLLP(VCLK_ECP_CNTL, VCLK_SRC_PPLL_CLK, ~VCLK_SRC_SEL_MASK);
dumpPLLRegs(io, "@@@ RestorePLL");
}
/**
* Finalize the restoring of the PLL registers
*
* @param io
*/
public final void finalizeRestorePLL(RadeonVgaIO io) {
io.setPLL(VCLK_ECP_CNTL, vclk_ecp_cntl);
}
/**
* Print the contents of the PLL registers on System.out.
*
* @param io
* @param msg
*/
private void dumpPLLRegs(RadeonVgaIO io, String msg) {
if (false) {
if (false) {
System.out.println(msg);
for (int i = 0; i <= 0x2e; i++) {
System.out.println("PLL[" + NumberUtils.hex(i, 2) + "]=" +
NumberUtils.hex(io.getPLL(i)));
}
} else {
System.out.println(msg);
System.out.println("PPLL_DIV_3 0x" + NumberUtils.hex(io.getPLL(PPLL_DIV_3)));
System.out.println("PPLL_REF_DIV 0x" + NumberUtils.hex(io.getPLL(PPLL_REF_DIV)));
System.out.println("HTOTAL_CNTL 0x" + NumberUtils.hex(io.getPLL(HTOTAL_CNTL)));
}
}
}
/**
* Calculate PLL dividers (freq is in 10kHz)
*
*/
public final void calcForConfiguration(FBInfo fbinfo, int freq, PLLInfo info) {
final FPIBlock fpi = fbinfo.getFpi();
if ((fpi != null) && fpi.useBiosDividers() && false) {
this.ppll_ref_div = fpi.getBiosRefDivider();
this.ppll_div_3 = (fpi.getBiosFeedbackDivider() | (fpi.getBiosPostDivider() << 16));
this.htotal_cntl = 0;
} else {
// formula is for generated frequency is:
// (ref_freq * feedback_div) / (ref_div * post_div )
// System.out.println("Req. freq=" + freq);
int pll_output_freq = 0;
final int ppll_max = info.getMaxPllFreq();
final int ppll_min = info.getMinPllFreq();
freq = Math.min(freq, ppll_max);
if (freq * 12 < ppll_min) {
freq = ppll_min / 12;
}
// System.out.println("Act. freq=" + freq);
// find proper divider by trial-and-error
int bitvalue;
for (bitvalue = 0; bitvalue < DIVIDERS.length; bitvalue++) {
pll_output_freq = DIVIDERS[bitvalue] * freq;
// System.out.println("bitvalue=" + bitvalue + ", post_div="
// + pll_output_freq);
if (pll_output_freq >= ppll_min && pll_output_freq <= ppll_max) {
break;
}
}
if (bitvalue >= DIVIDERS.length) {
throw new IllegalArgumentException("Frequency (" + freq +
" kHz) is out of PLL range!");
}
final int ref_clk = info.getRef_clk();
final int ref_div = info.getRef_div();
final int feedback_div = (ref_div * pll_output_freq) / ref_clk;
this.ppll_ref_div = ref_div;
this.ppll_div_3 = (feedback_div | (bitvalue << 16));
this.htotal_cntl = 0;
@SuppressWarnings("unused")
final int vclk_freq = (ref_clk * feedback_div) / (ref_div * pll_output_freq);
// System.out.println("vclk_freq=" + vclk_freq + ", fbdiv="
// + feedback_div + ", postdiv=" + pll_output_freq
// + ", bitvalue=" + bitvalue);
}
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "ppll_ref_div:" + ppll_ref_div + ", ppll_div_3:0x" + NumberUtils.hex(ppll_div_3) +
", htotal_cntl:0x" + NumberUtils.hex(htotal_cntl);
}
@SuppressWarnings("unused")
private final void PLLWriteUpdate(RadeonVgaIO io) {
PLLWaitForReadUpdateComplete(io);
io.setPLLP(crtc2 ? P2PLL_REF_DIV : PPLL_REF_DIV, PPLL_ATOMIC_UPDATE_W,
~PPLL_ATOMIC_UPDATE_W);
}
private final void PLLWaitForReadUpdateComplete(RadeonVgaIO io) {
int i;
// we should wait forever, but
// 1. this is unsafe
// 2. some r300 loop forever (reported by XFree86)
for (i = 0; i < 10000; ++i) {
if ((io.getPLL(crtc2 ? P2PLL_REF_DIV : PPLL_REF_DIV) & PPLL_ATOMIC_UPDATE_R) == 0) {
return;
}
}
}
}