#include "radeon_accelerant.h"
#include "pll_regs.h"
#include "pll_access.h"
#include "utils.h"
#include <stdlib.h>
#include "set_mode.h"
static void Radeon_PLLWaitForReadUpdateComplete(
accelerator_info *ai, int crtc_idx )
{
int i;
for( i = 0; i < 10000; ++i ) {
if( (Radeon_INPLL( ai->regs, ai->si->asic, crtc_idx == 0 ? RADEON_PPLL_REF_DIV : RADEON_P2PLL_REF_DIV )
& RADEON_PPLL_ATOMIC_UPDATE_R) == 0 )
return;
}
}
static void Radeon_PLLWriteUpdate(
accelerator_info *ai, int crtc_idx )
{
Radeon_PLLWaitForReadUpdateComplete( ai, crtc_idx );
Radeon_OUTPLLP( ai->regs, ai->si->asic,
crtc_idx == 0 ? RADEON_PPLL_REF_DIV : RADEON_P2PLL_REF_DIV,
RADEON_PPLL_ATOMIC_UPDATE_W,
~RADEON_PPLL_ATOMIC_UPDATE_W );
}
void Radeon_CalcPLLDividers(
const pll_info *pll, uint32 freq, uint fixed_post_div, pll_dividers *dividers )
{
int
min_post_div_idx, max_post_div_idx,
post_div_idx, extra_post_div_idx,
best_post_div_idx, best_extra_post_div_idx;
uint32
best_ref_div, best_feedback_div, best_freq;
int32
best_error, best_vco_dev;
best_error = 999999999;
best_post_div_idx = 0;
best_extra_post_div_idx = 0;
best_ref_div = 1;
best_feedback_div = 1;
best_freq = 1;
best_vco_dev = 1;
if( fixed_post_div == 0 ) {
min_post_div_idx = 0;
for(
max_post_div_idx = 0;
pll->post_divs[max_post_div_idx].divider != 0;
++max_post_div_idx )
;
--max_post_div_idx;
} else {
for(
min_post_div_idx = 0;
pll->post_divs[min_post_div_idx].divider != fixed_post_div;
++min_post_div_idx )
;
max_post_div_idx = min_post_div_idx;
}
for( extra_post_div_idx = 0; pll->extra_post_divs[extra_post_div_idx].divider != 0; ++extra_post_div_idx ) {
for( post_div_idx = min_post_div_idx; post_div_idx <= max_post_div_idx; ++post_div_idx ) {
uint32 ref_div;
uint32 post_div =
pll->post_divs[post_div_idx].divider
* pll->extra_post_divs[extra_post_div_idx].divider;
uint32 vco = (freq / 10000) * post_div;
if( vco < pll->vco_min || vco > pll->vco_max )
continue;
for( ref_div = pll->min_ref_div; ref_div <= pll->max_ref_div; ++ref_div ) {
uint32 feedback_div, cur_freq;
int32 error, vco_dev;
uint32 pll_in = pll->ref_freq / ref_div;
if( pll_in < pll->pll_in_min || pll_in > pll->pll_in_max )
continue;
feedback_div = RoundDiv64(
(int64)freq * ref_div * post_div,
pll->ref_freq * 10000 * pll->extra_feedback_div);
if( feedback_div < pll->min_feedback_div ||
feedback_div > pll->max_feedback_div )
continue;
cur_freq = RoundDiv64(
(int64)pll->ref_freq * 10000 * feedback_div * pll->extra_feedback_div,
ref_div * post_div );
error = abs( (int32)cur_freq - (int32)freq );
vco_dev = abs( (int32)vco - (int32)(pll->best_vco) );
if( (pll->best_vco == 0 && error < best_error) ||
(pll->best_vco != 0 &&
(error < best_error - 100 ||
(abs( error - best_error ) < 100 && vco_dev < best_vco_dev ))))
{
best_post_div_idx = post_div_idx;
best_extra_post_div_idx = extra_post_div_idx;
best_ref_div = ref_div;
best_feedback_div = feedback_div;
best_freq = cur_freq;
best_error = error;
best_vco_dev = vco_dev;
}
}
}
}
dividers->post_code = pll->post_divs[best_post_div_idx].code;
dividers->post = pll->post_divs[best_post_div_idx].divider;
dividers->extra_post_code = pll->post_divs[best_extra_post_div_idx].code;
dividers->extra_post = pll->post_divs[best_extra_post_div_idx].divider;
dividers->ref = best_ref_div;
dividers->feedback = best_feedback_div;
dividers->freq = best_freq;
}
void Radeon_MatchCRTPLL(
const pll_info *pll,
uint32 tv_v_total, uint32 tv_h_total, uint32 tv_frame_size_adjust, uint32 freq,
const display_mode *mode, uint32 max_v_tweak, uint32 max_h_tweak,
uint32 max_frame_rate_drift, uint32 fixed_post_div,
pll_dividers *dividers,
display_mode *tweaked_mode )
{
uint32 v_tweak;
int32 v_tweak_dir;
uint32 pix_per_tv_frame;
SHOW_FLOW( 2, "fixed post divider: %d", fixed_post_div );
pix_per_tv_frame = tv_v_total * tv_h_total + tv_frame_size_adjust;
for( v_tweak = 0; v_tweak <= max_v_tweak; ++v_tweak ) {
for( v_tweak_dir = -1; v_tweak_dir <= 1; v_tweak_dir += 2 ) {
uint32 h_tweak;
int32 h_tweak_dir;
uint32 v_total = mode->timing.v_total + v_tweak * v_tweak_dir;
for( h_tweak = 0; h_tweak <= max_h_tweak; ++h_tweak ) {
for( h_tweak_dir = -1; h_tweak_dir <= 1; h_tweak_dir += 2 ) {
uint32 pix_per_crt_frame, frame_rate_drift;
uint32 crt_freq;
uint32 abs_crt_error;
uint32 h_total = mode->timing.h_total + h_tweak * h_tweak_dir;
pix_per_crt_frame = v_total * h_total;
crt_freq = (uint64)freq * pix_per_crt_frame * 2 / pix_per_tv_frame;
Radeon_CalcPLLDividers( pll, crt_freq, fixed_post_div, dividers );
abs_crt_error = abs( (int32)(dividers->freq) - (int32)crt_freq );
frame_rate_drift = (uint64)abs_crt_error * pix_per_tv_frame / freq;
if( frame_rate_drift <= max_frame_rate_drift ) {
SHOW_INFO( 2, "frame_rate_drift=%d, crt_freq=%d, v_total=%d, h_total=%d",
frame_rate_drift, crt_freq, v_total, h_total );
tweaked_mode->timing.pixel_clock = crt_freq;
tweaked_mode->timing.v_total = v_total;
tweaked_mode->timing.h_total = h_total;
return;
}
}
}
}
}
}
static pll_divider_map post_divs[] = {
{ 1, 0 },
{ 2, 1 },
{ 4, 2 },
{ 8, 3 },
{ 3, 4 },
{ 6, 6 },
{ 12, 7 },
{ 0, 0 }
};
static pll_divider_map extra_post_divs[] = {
{ 1, 1 },
{ 0, 0 }
};
static pll_divider_map external_extra_post_divs[] = {
{ 1, 0 },
{ 2, 1 },
{ 0, 0 }
};
static pll_divider_map tv_post_divs[] = {
{ 1, 1 },
{ 2, 2 },
{ 3, 3 },
{ 4, 4 },
{ 5, 5 },
{ 6, 6 },
{ 7, 7 },
{ 8, 8 },
{ 9, 9 },
{ 10, 10 },
{ 11, 11 },
{ 12, 12 },
{ 13, 13 },
{ 14, 14 },
{ 15, 15 },
{ 0, 0 }
};
void Radeon_GetTVPLLConfiguration( const general_pll_info *general_pll, pll_info *pll,
bool internal_encoder )
{
pll->post_divs = tv_post_divs;
pll->extra_post_divs = internal_encoder ? extra_post_divs : external_extra_post_divs;
pll->ref_freq = general_pll->ref_freq;
pll->vco_min = 10000;
pll->vco_max = 25000;
pll->min_ref_div = 4;
pll->max_ref_div = 0x3ff;
pll->pll_in_min = 20;
pll->pll_in_max = 100;
pll->extra_feedback_div = 1;
pll->min_feedback_div = 4;
pll->max_feedback_div = 0x7ff;
pll->best_vco = 21000;
}
void Radeon_GetTVCRTPLLConfiguration( const general_pll_info *general_pll, pll_info *pll,
bool internal_tv_encoder )
{
pll->post_divs = post_divs;
pll->extra_post_divs = extra_post_divs;
pll->ref_freq = general_pll->ref_freq;
pll->vco_min = 4000;
pll->vco_max = general_pll->max_pll_freq;
pll->min_ref_div = 2;
pll->max_ref_div = 0x3ff;
pll->pll_in_min = 20;
pll->pll_in_max = 100;
pll->extra_feedback_div = 1;
pll->min_feedback_div = 4;
pll->max_feedback_div = 0x7ff;
pll->best_vco = internal_tv_encoder ? 17500 : 21000;
}
void Radeon_CalcCRTPLLDividers(
const general_pll_info *general_pll, const display_mode *mode, pll_dividers *dividers )
{
pll_info pll;
pll.post_divs = post_divs;
pll.extra_post_divs = extra_post_divs;
pll.ref_freq = general_pll->ref_freq;
pll.vco_min = general_pll->min_pll_freq;
pll.vco_max = general_pll->max_pll_freq;
pll.min_ref_div = 2;
pll.max_ref_div = 0x3ff;
pll.pll_in_min = 40;
pll.pll_in_max = 100;
pll.extra_feedback_div = 1;
pll.min_feedback_div = 4;
pll.max_feedback_div = 0x7ff;
pll.best_vco = 0;
SHOW_FLOW( 2, "freq=%ld", mode->timing.pixel_clock );
Radeon_CalcPLLDividers( &pll, mode->timing.pixel_clock, 0, dividers );
}
void Radeon_CalcPLLRegisters(
const display_mode *mode, const pll_dividers *dividers, pll_regs *values )
{
values->dot_clock_freq = dividers->freq;
values->feedback_div = dividers->feedback;
values->post_div = dividers->post;
values->pll_output_freq = dividers->freq * dividers->post;
values->ppll_ref_div = dividers->ref;
values->ppll_div_3 = (dividers->feedback | (dividers->post_code << 16));
values->htotal_cntl = mode->timing.h_total & 7;
SHOW_FLOW( 2, "dot_clock_freq=%ld, pll_output_freq=%ld, ref_div=%d, feedback_div=%d, post_div=%d",
values->dot_clock_freq, values->pll_output_freq,
values->ppll_ref_div, values->feedback_div, values->post_div );
}
void Radeon_ProgramPLL(
accelerator_info *ai, int crtc_idx, pll_regs *values )
{
vuint8 *regs = ai->regs;
radeon_type asic = ai->si->asic;
SHOW_FLOW0( 2, "" );
Radeon_OUTPLLP( regs, asic, crtc_idx == 0 ? RADEON_VCLK_ECP_CNTL : RADEON_PIXCLKS_CNTL,
RADEON_VCLK_SRC_CPU_CLK, ~RADEON_VCLK_SRC_SEL_MASK );
Radeon_OUTPLLP( regs, asic,
crtc_idx == 0 ? RADEON_PPLL_CNTL : RADEON_P2PLL_CNTL,
RADEON_PPLL_RESET
| RADEON_PPLL_ATOMIC_UPDATE_EN
| RADEON_PPLL_VGA_ATOMIC_UPDATE_EN,
~(RADEON_PPLL_RESET
| RADEON_PPLL_ATOMIC_UPDATE_EN
| RADEON_PPLL_VGA_ATOMIC_UPDATE_EN) );
OUTREGP( regs, RADEON_CLOCK_CNTL_INDEX,
RADEON_PLL_DIV_SEL_DIV3,
~RADEON_PLL_DIV_SEL_MASK );
RADEONPllErrataAfterIndex(regs, asic);
if( ai->si->new_pll && crtc_idx == 0 ) {
Radeon_OUTPLLP( regs, asic,
RADEON_PPLL_REF_DIV,
values->ppll_ref_div << RADEON_PPLL_REF_DIV_ACC_SHIFT,
~RADEON_PPLL_REF_DIV_ACC_MASK );
} else {
Radeon_OUTPLLP( regs, asic,
crtc_idx == 0 ? RADEON_PPLL_REF_DIV : RADEON_P2PLL_REF_DIV,
values->ppll_ref_div,
~RADEON_PPLL_REF_DIV_MASK );
}
Radeon_OUTPLLP( regs, asic,
crtc_idx == 0 ? RADEON_PPLL_DIV_3 : RADEON_P2PLL_DIV_0,
values->ppll_div_3,
~RADEON_PPLL_FB3_DIV_MASK );
Radeon_OUTPLLP( regs, asic,
crtc_idx == 0 ? RADEON_PPLL_DIV_3 : RADEON_P2PLL_DIV_0,
values->ppll_div_3,
~RADEON_PPLL_POST3_DIV_MASK );
Radeon_PLLWriteUpdate( ai, crtc_idx );
Radeon_PLLWaitForReadUpdateComplete( ai, crtc_idx );
Radeon_OUTPLL( regs, asic,
crtc_idx == 0 ? RADEON_HTOTAL_CNTL : RADEON_HTOTAL2_CNTL,
values->htotal_cntl );
Radeon_OUTPLLP( regs, asic,
crtc_idx == 0 ? RADEON_PPLL_CNTL : RADEON_P2PLL_CNTL, 0,
~(RADEON_PPLL_RESET
| RADEON_PPLL_SLEEP
| RADEON_PPLL_ATOMIC_UPDATE_EN
| RADEON_PPLL_VGA_ATOMIC_UPDATE_EN) );
snooze( 5000 );
Radeon_OUTPLLP( regs, asic,
crtc_idx == 0 ? RADEON_VCLK_ECP_CNTL : RADEON_PIXCLKS_CNTL,
RADEON_VCLK_SRC_PPLL_CLK, ~RADEON_VCLK_SRC_SEL_MASK );
}