#include "pll.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <Debug.h>
#include <create_display_modes.h>
#include <ddc.h>
#include <edid.h>
#include <validate_display_mode.h>
#include "accelerant_protos.h"
#include "accelerant.h"
#include "utility.h"
#undef TRACE
#define TRACE_MODE
#ifdef TRACE_MODE
# define TRACE(x...) _sPrintf("intel_extreme: " x)
#else
# define TRACE(x...)
#endif
#define ERROR(x...) _sPrintf("intel_extreme: " x)
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
static pll_limits kLimits85x = {
{ 4, 2, 2, 4, 96, 20, 8},
{128, 33, 4, 18, 140, 28, 18},
165000, 908000, 1512000
};
static pll_limits kLimits9xxSdvo = {
{ 5, 1, 5, 3, 70, 10, 5},
{ 80, 8, 10, 8, 120, 20, 9},
200000, 1400000, 2800000
};
static pll_limits kLimits9xxLvds = {
{ 7, 1, 7, 3, 70, 10, 5},
{ 98, 8, 14, 8, 120, 20, 9},
112000, 1400000, 2800000
};
static pll_limits kLimitsG4xSdvo = {
{ 10, 1, 10, 3, 104, 19, 7},
{ 80, 8, 10, 8, 138, 25, 13},
270000, 1750000, 3500000
};
#if 0
static pll_limits kLimitsG4xHdmi = {
{ 5, 1, 5, 3, 104, 18, 7},
{ 80, 8, 10, 8, 138, 25, 13},
165000, 1750000, 3500000
};
#endif
static pll_limits kLimitsG4xLvdsSingle = {
{ 28, 2, 14, 3, 104, 19, 7},
{112, 8, 14, 8, 138, 25, 13},
0, 1750000, 3500000
};
static pll_limits kLimitsG4xLvdsDual = {
{ 14, 2, 7, 3, 104, 19, 7},
{ 42, 6, 7, 8, 138, 25, 13},
0, 1750000, 3500000
};
static pll_limits kLimitsIlkDac = {
{ 5, 1, 5, 3, 79, 14, 7},
{ 80, 8, 10, 7, 127, 24, 11},
225000, 1760000, 3510000
};
static pll_limits kLimitsIlkLvdsSingle = {
{ 28, 2, 14, 3, 79, 14, 7},
{112, 8, 14, 5, 118, 24, 11},
225000, 1760000, 3510000
};
static pll_limits kLimitsIlkLvdsDual = {
{ 14, 2, 7, 3, 79, 14, 7},
{ 56, 8, 7, 5, 127, 24, 11},
225000, 1760000, 3510000
};
static pll_limits kLimitsIlkLvdsSingle100 = {
{ 28, 2, 14, 3, 79, 14, 7},
{112, 8, 14, 4, 126, 24, 11},
225000, 1760000, 3510000
};
static pll_limits kLimitsIlkLvdsDual100 = {
{ 14, 2, 7, 3, 79, 14, 7},
{ 42, 6, 7, 5, 126, 24, 11},
225000, 1760000, 3510000
};
#if 0
static pll_limits kLimitsChv = {
{ 0, 2, 1, 1, 79, 2, 24 << 22},
{ 0, 4, 14, 1, 127, 2, 175 << 22},
0, 4800000, 6480000
};
static pll_limits kLimitsVlv = {
{ 0, 2, 2, 1, 79, 2, 11},
{ 0, 3, 20, 7, 127, 3, 156},
0, 4000000, 6000000
};
static pll_limits kLimitsBxt = {
{ 0, 2, 1, 1, 0, 2, 2 << 22},
{ 0, 4, 20, 1, 0, 2, 255 << 22},
0, 4800000, 6700000
};
#endif
static pll_limits kLimitsPinSdvo = {
{ 5, 1, 5, 3, 2, 0, 0},
{ 80, 8, 10, 6, 256, 0, 254},
200000, 1700000, 3500000
};
static pll_limits kLimitsPinLvds = {
{ 7, 1, 14, 3, 2, 0, 0},
{112, 8, 14, 6, 256, 0, 254},
112000, 1700000, 3500000
};
static bool
lvds_dual_link(display_timing* current)
{
float requestedPixelClock = current->pixel_clock / 1000.0f;
if (requestedPixelClock > 112.999)
return true;
return ((read32(INTEL_DIGITAL_LVDS_PORT) & LVDS_CLKB_POWER_MASK)
== LVDS_CLKB_POWER_UP);
}
bool
valid_pll_divisors(pll_divisors* divisors, pll_limits* limits)
{
pll_info &info = gInfo->shared_info->pll_info;
uint32 vco = info.reference_frequency * divisors->m / divisors->n;
uint32 frequency = vco / divisors->p;
if (divisors->p < limits->min.p || divisors->p > limits->max.p
|| divisors->m < limits->min.m || divisors->m > limits->max.m
|| vco < limits->min_vco || vco > limits->max_vco
|| frequency < info.min_frequency || frequency > info.max_frequency)
return false;
return true;
}
static void
compute_pll_p2(display_timing* current, pll_divisors* divisors,
pll_limits* limits, bool isLVDS)
{
if (isLVDS) {
if (lvds_dual_link(current)) {
divisors->p2 = limits->min.p2;
} else {
divisors->p2 = limits->max.p2;
}
} else {
if (current->pixel_clock < limits->dot_limit) {
divisors->p2 = limits->max.p2;
} else {
divisors->p2 = limits->min.p2;
}
}
}
static uint32
compute_pll_m(pll_divisors* divisors)
{
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_CHV)
|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV)) {
return divisors->m1 * divisors->m2;
}
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_PIN))
return divisors->m2;
return 5 * divisors->m1 + divisors->m2;
}
static uint32
compute_pll_p(pll_divisors* divisors)
{
return divisors->p1 * divisors->p2;
}
static void
compute_dpll_g4x(display_timing* current, pll_divisors* divisors, bool isLVDS)
{
float requestedPixelClock = current->pixel_clock / 1000.0f;
float referenceClock
= gInfo->shared_info->pll_info.reference_frequency / 1000.0f;
TRACE("%s: required MHz: %g, reference clock: %g\n", __func__,
requestedPixelClock, referenceClock);
pll_limits limits;
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_G4x)) {
if (isLVDS) {
if (lvds_dual_link(current))
memcpy(&limits, &kLimitsG4xLvdsDual, sizeof(pll_limits));
else
memcpy(&limits, &kLimitsG4xLvdsSingle, sizeof(pll_limits));
} else
memcpy(&limits, &kLimitsG4xSdvo, sizeof(pll_limits));
} else {
if (isLVDS) {
if (lvds_dual_link(current)) {
if (referenceClock == 100.0)
memcpy(&limits, &kLimitsIlkLvdsDual100, sizeof(pll_limits));
else
memcpy(&limits, &kLimitsIlkLvdsDual, sizeof(pll_limits));
} else {
if (referenceClock == 100.0) {
memcpy(&limits, &kLimitsIlkLvdsSingle100,
sizeof(pll_limits));
} else {
memcpy(&limits, &kLimitsIlkLvdsSingle, sizeof(pll_limits));
}
}
} else {
memcpy(&limits, &kLimitsIlkDac, sizeof(pll_limits));
}
}
compute_pll_p2(current, divisors, &limits, isLVDS);
TRACE("PLL limits, min: p %" B_PRId32 " (p1 %" B_PRId32 ", "
"p2 %" B_PRId32 "), n %" B_PRId32 ", m %" B_PRId32 " "
"(m1 %" B_PRId32 ", m2 %" B_PRId32 ")\n", limits.min.p,
limits.min.p1, limits.min.p2, limits.min.n, limits.min.m,
limits.min.m1, limits.min.m2);
TRACE("PLL limits, max: p %" B_PRId32 " (p1 %" B_PRId32 ", "
"p2 %" B_PRId32 "), n %" B_PRId32 ", m %" B_PRId32 " "
"(m1 %" B_PRId32 ", m2 %" B_PRId32 ")\n", limits.max.p,
limits.max.p1, limits.max.p2, limits.max.n, limits.max.m,
limits.max.m1, limits.max.m2);
float best = requestedPixelClock;
pll_divisors bestDivisors;
for (divisors->n = limits.min.n; divisors->n <= limits.max.n;
divisors->n++) {
for (divisors->m1 = limits.max.m1; divisors->m1 >= limits.min.m1;
divisors->m1--) {
for (divisors->m2 = limits.max.m2; divisors->m2 >= limits.min.m2;
divisors->m2--) {
for (divisors->p1 = limits.max.p1;
divisors->p1 >= limits.min.p1; divisors->p1--) {
divisors->m = compute_pll_m(divisors);
divisors->p = compute_pll_p(divisors);
if (!valid_pll_divisors(divisors, &limits))
continue;
float error = fabs(requestedPixelClock
- (referenceClock * divisors->m)
/ (divisors->n * divisors->p));
if (error < best) {
best = error;
bestDivisors = *divisors;
if (error == 0)
break;
}
}
}
}
}
*divisors = bestDivisors;
TRACE("%s: best MHz: %g (error: %g)\n", __func__,
(referenceClock * divisors->m) / (divisors->n * divisors->p),
best);
}
static void
compute_dpll_9xx(display_timing* current, pll_divisors* divisors, bool isLVDS)
{
float requestedPixelClock = current->pixel_clock / 1000.0f;
float referenceClock
= gInfo->shared_info->pll_info.reference_frequency / 1000.0f;
TRACE("%s: required MHz: %g\n", __func__, requestedPixelClock);
pll_limits limits;
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_PIN)) {
if (isLVDS)
memcpy(&limits, &kLimitsPinLvds, sizeof(pll_limits));
else
memcpy(&limits, &kLimitsPinSdvo, sizeof(pll_limits));
} else if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_85x)) {
memcpy(&limits, &kLimits85x, sizeof(pll_limits));
} else {
if (isLVDS)
memcpy(&limits, &kLimits9xxLvds, sizeof(pll_limits));
else
memcpy(&limits, &kLimits9xxSdvo, sizeof(pll_limits));
}
compute_pll_p2(current, divisors, &limits, isLVDS);
TRACE("PLL limits, min: p %" B_PRId32 " (p1 %" B_PRId32 ", "
"p2 %" B_PRId32 "), n %" B_PRId32 ", m %" B_PRId32 " "
"(m1 %" B_PRId32 ", m2 %" B_PRId32 ")\n", limits.min.p,
limits.min.p1, limits.min.p2, limits.min.n, limits.min.m,
limits.min.m1, limits.min.m2);
TRACE("PLL limits, max: p %" B_PRId32 " (p1 %" B_PRId32 ", "
"p2 %" B_PRId32 "), n %" B_PRId32 ", m %" B_PRId32 " "
"(m1 %" B_PRId32 ", m2 %" B_PRId32 ")\n", limits.max.p,
limits.max.p1, limits.max.p2, limits.max.n, limits.max.m,
limits.max.m1, limits.max.m2);
bool is_pine = gInfo->shared_info->device_type.InGroup(INTEL_GROUP_PIN);
float best = requestedPixelClock;
pll_divisors bestDivisors;
memset(&bestDivisors, 0, sizeof(bestDivisors));
for (divisors->m1 = limits.min.m1; divisors->m1 <= limits.max.m1;
divisors->m1++) {
for (divisors->m2 = limits.min.m2; divisors->m2 <= limits.max.m2
&& ((divisors->m2 < divisors->m1) || is_pine); divisors->m2++) {
for (divisors->n = limits.min.n; divisors->n <= limits.max.n;
divisors->n++) {
for (divisors->p1 = limits.min.p1;
divisors->p1 <= limits.max.p1; divisors->p1++) {
divisors->m = compute_pll_m(divisors);
divisors->p = compute_pll_p(divisors);
if (!valid_pll_divisors(divisors, &limits))
continue;
float error = fabs(requestedPixelClock
- (referenceClock * divisors->m)
/ (divisors->n * divisors->p));
if (error < best) {
best = error;
bestDivisors = *divisors;
if (error == 0)
break;
}
}
}
}
}
*divisors = bestDivisors;
if (best == requestedPixelClock)
debugger("No valid PLL configuration found");
else {
TRACE("%s: best MHz: %g (error: %g)\n", __func__,
(referenceClock * divisors->m) / (divisors->n * divisors->p),
best);
}
}
void
compute_pll_divisors(display_timing* current, pll_divisors* divisors, bool isLVDS)
{
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_G4x)
|| (gInfo->shared_info->pch_info != INTEL_PCH_NONE)) {
compute_dpll_g4x(current, divisors, isLVDS);
} else if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_CHV)) {
ERROR("%s: TODO: CherryView\n", __func__);
} else if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV)) {
ERROR("%s: TODO: VallyView\n", __func__);
} else
compute_dpll_9xx(current, divisors, isLVDS);
TRACE("%s: found: p = %" B_PRId32 " (p1 = %" B_PRId32 ", "
"p2 = %" B_PRId32 "), n = %" B_PRId32 ", m = %" B_PRId32 " "
"(m1 = %" B_PRId32 ", m2 = %" B_PRId32 ")\n", __func__,
divisors->p, divisors->p1, divisors->p2, divisors->n,
divisors->m, divisors->m1, divisors->m2);
}
void
refclk_activate_ilk(bool hasPanel)
{
CALLED();
bool wantsSSC;
bool hasCK505;
if (gInfo->shared_info->pch_info == INTEL_PCH_IBX) {
TRACE("%s: Generation 5 graphics\n", __func__);
hasCK505 = false;
wantsSSC = hasCK505;
} else {
if (gInfo->shared_info->device_type.Generation() == 6) {
TRACE("%s: Generation 6 graphics\n", __func__);
} else {
TRACE("%s: Generation 7 graphics\n", __func__);
}
hasCK505 = false;
wantsSSC = true;
}
uint32 clkRef = read32(PCH_DREF_CONTROL);
uint32 newRef = clkRef;
TRACE("%s: PCH_DREF_CONTROL before: 0x%" B_PRIx32 "\n", __func__, clkRef);
newRef &= ~DREF_NONSPREAD_SOURCE_MASK;
if (hasCK505)
newRef |= DREF_NONSPREAD_CK505_ENABLE;
else
newRef |= DREF_NONSPREAD_SOURCE_ENABLE;
newRef &= ~DREF_SSC_SOURCE_MASK;
newRef &= ~DREF_CPU_SOURCE_OUTPUT_MASK;
newRef &= ~DREF_SSC1_ENABLE;
if (newRef == clkRef) {
TRACE("%s: No changes to reference clock.\n", __func__);
return;
}
if (hasPanel) {
newRef &= ~DREF_SSC_SOURCE_MASK;
newRef |= DREF_SSC_SOURCE_ENABLE;
if (wantsSSC)
newRef |= DREF_SSC1_ENABLE;
else
newRef &= ~DREF_SSC1_ENABLE;
write32(PCH_DREF_CONTROL, newRef);
read32(PCH_DREF_CONTROL);
TRACE("%s: PCH_DREF_CONTROL after SSC on/off: 0x%" B_PRIx32 "\n",
__func__, read32(PCH_DREF_CONTROL));
spin(200);
newRef &= ~DREF_CPU_SOURCE_OUTPUT_MASK;
bool hasEDP = true;
if (hasEDP) {
if (wantsSSC)
newRef |= DREF_CPU_SOURCE_OUTPUT_DOWNSPREAD;
else
newRef |= DREF_CPU_SOURCE_OUTPUT_NONSPREAD;
} else
newRef |= DREF_CPU_SOURCE_OUTPUT_DISABLE;
write32(PCH_DREF_CONTROL, newRef);
read32(PCH_DREF_CONTROL);
TRACE("%s: PCH_DREF_CONTROL after done: 0x%" B_PRIx32 "\n",
__func__, read32(PCH_DREF_CONTROL));
spin(200);
} else {
newRef &= ~DREF_CPU_SOURCE_OUTPUT_MASK;
newRef |= DREF_CPU_SOURCE_OUTPUT_DISABLE;
write32(PCH_DREF_CONTROL, newRef);
read32(PCH_DREF_CONTROL);
TRACE("%s: PCH_DREF_CONTROL after disable CPU output: 0x%" B_PRIx32 "\n",
__func__, read32(PCH_DREF_CONTROL));
spin(200);
if (!wantsSSC) {
newRef &= ~DREF_SSC_SOURCE_MASK;
newRef |= DREF_SSC_SOURCE_DISABLE;
newRef &= ~DREF_SSC1_ENABLE;
write32(PCH_DREF_CONTROL, newRef);
read32(PCH_DREF_CONTROL);
TRACE("%s: PCH_DREF_CONTROL after disable SSC: 0x%" B_PRIx32 "\n",
__func__, read32(PCH_DREF_CONTROL));
spin(200);
}
}
}
#define LC_FREQ 2700
#define LC_FREQ_2K (uint64)(LC_FREQ * 2000)
#define P_MIN 2
#define P_MAX 64
#define P_INC 2
#define REF_MIN 48
#define REF_MAX 400
#define VCO_MIN 2400
#define VCO_MAX 4800
static uint64 AbsSubtr64(uint64 nr1, uint64 nr2)
{
if (nr1 >= nr2) {
return nr1 - nr2;
} else {
return nr2 - nr1;
}
}
struct hsw_wrpll_rnp {
unsigned p, n2, r2;
};
static unsigned hsw_wrpll_get_budget_for_freq(int clock)
{
unsigned budget;
switch (clock) {
case 25175000:
case 25200000:
case 27000000:
case 27027000:
case 37762500:
case 37800000:
case 40500000:
case 40541000:
case 54000000:
case 54054000:
case 59341000:
case 59400000:
case 72000000:
case 74176000:
case 74250000:
case 81000000:
case 81081000:
case 89012000:
case 89100000:
case 108000000:
case 108108000:
case 111264000:
case 111375000:
case 148352000:
case 148500000:
case 162000000:
case 162162000:
case 222525000:
case 222750000:
case 296703000:
case 297000000:
budget = 0;
break;
case 233500000:
case 245250000:
case 247750000:
case 253250000:
case 298000000:
budget = 1500;
break;
case 169128000:
case 169500000:
case 179500000:
case 202000000:
budget = 2000;
break;
case 256250000:
case 262500000:
case 270000000:
case 272500000:
case 273750000:
case 280750000:
case 281250000:
case 286000000:
case 291750000:
budget = 4000;
break;
case 267250000:
case 268500000:
budget = 5000;
break;
default:
budget = 1000;
break;
}
return budget;
}
static void hsw_wrpll_update_rnp(uint64 freq2k, unsigned int budget,
unsigned int r2, unsigned int n2,
unsigned int p,
struct hsw_wrpll_rnp *best)
{
uint64 a, b, c, d, diff, diff_best;
if (best->p == 0) {
best->p = p;
best->n2 = n2;
best->r2 = r2;
return;
}
a = freq2k * budget * p * r2;
b = freq2k * budget * best->p * best->r2;
diff = AbsSubtr64((uint64)freq2k * p * r2, LC_FREQ_2K * n2);
diff_best = AbsSubtr64((uint64)freq2k * best->p * best->r2,
LC_FREQ_2K * best->n2);
c = 1000000 * diff;
d = 1000000 * diff_best;
if (a < c && b < d) {
if (best->p * best->r2 * diff < p * r2 * diff_best) {
best->p = p;
best->n2 = n2;
best->r2 = r2;
}
} else if (a >= c && b < d) {
best->p = p;
best->n2 = n2;
best->r2 = r2;
} else if (a >= c && b >= d) {
if (n2 * best->r2 * best->r2 > best->n2 * r2 * r2) {
best->p = p;
best->n2 = n2;
best->r2 = r2;
}
}
}
void
hsw_ddi_calculate_wrpll(int clock ,
unsigned *r2_out, unsigned *n2_out, unsigned *p_out)
{
uint64 freq2k;
unsigned p, n2, r2;
struct hsw_wrpll_rnp best = { 0, 0, 0 };
unsigned budget;
freq2k = clock / 100;
budget = hsw_wrpll_get_budget_for_freq(clock);
if (freq2k == 5400000) {
*n2_out = 2;
*p_out = 1;
*r2_out = 2;
return;
}
for (r2 = LC_FREQ * 2 / REF_MAX + 1;
r2 <= LC_FREQ * 2 / REF_MIN;
r2++) {
for (n2 = VCO_MIN * r2 / LC_FREQ + 1;
n2 <= VCO_MAX * r2 / LC_FREQ;
n2++) {
for (p = P_MIN; p <= P_MAX; p += P_INC)
hsw_wrpll_update_rnp(freq2k, budget,
r2, n2, p, &best);
}
}
*n2_out = best.n2;
*p_out = best.p;
*r2_out = best.r2;
}
struct skl_wrpll_context {
uint64 min_deviation;
uint64 central_freq;
uint64 dco_freq;
unsigned int p;
};
#define SKL_DCO_MAX_PDEVIATION 100
#define SKL_DCO_MAX_NDEVIATION 600
static void skl_wrpll_try_divider(struct skl_wrpll_context *ctx,
uint64 central_freq,
uint64 dco_freq,
unsigned int divider)
{
uint64 deviation;
deviation = ((uint64)10000 * AbsSubtr64(dco_freq, central_freq)
/ central_freq);
if (dco_freq >= central_freq) {
if (deviation < SKL_DCO_MAX_PDEVIATION &&
deviation < ctx->min_deviation) {
ctx->min_deviation = deviation;
ctx->central_freq = central_freq;
ctx->dco_freq = dco_freq;
ctx->p = divider;
TRACE("%s: DCO central frequency %" B_PRIu64 "Hz\n", __func__, central_freq);
TRACE("%s: DCO frequency %" B_PRIu64 "Hz\n", __func__, dco_freq);
TRACE("%s: positive offset accepted, deviation %" B_PRIu64 "\n",
__func__, deviation);
}
} else if (deviation < SKL_DCO_MAX_NDEVIATION &&
deviation < ctx->min_deviation) {
ctx->min_deviation = deviation;
ctx->central_freq = central_freq;
ctx->dco_freq = dco_freq;
ctx->p = divider;
TRACE("%s: DCO central frequency %" B_PRIu64 "Hz\n", __func__, central_freq);
TRACE("%s: DCO frequency %" B_PRIu64 "Hz\n", __func__, dco_freq);
TRACE("%s: negative offset accepted, deviation %" B_PRIu64 "\n",
__func__, deviation);
}
}
static void skl_wrpll_get_multipliers(unsigned int p,
unsigned int *p0 ,
unsigned int *p1 ,
unsigned int *p2 )
{
if (p % 2 == 0) {
unsigned int half = p / 2;
if (half == 1 || half == 2 || half == 3 || half == 5) {
*p0 = 2;
*p1 = 1;
*p2 = half;
} else if (half % 2 == 0) {
*p0 = 2;
*p1 = half / 2;
*p2 = 2;
} else if (half % 3 == 0) {
*p0 = 3;
*p1 = half / 3;
*p2 = 2;
} else if (half % 7 == 0) {
*p0 = 7;
*p1 = half / 7;
*p2 = 2;
}
} else if (p == 3 || p == 9) {
*p0 = 3;
*p1 = 1;
*p2 = p / 3;
} else if (p == 5 || p == 7) {
*p0 = p;
*p1 = 1;
*p2 = 1;
} else if (p == 15) {
*p0 = 3;
*p1 = 1;
*p2 = 5;
} else if (p == 21) {
*p0 = 7;
*p1 = 1;
*p2 = 3;
} else if (p == 35) {
*p0 = 7;
*p1 = 1;
*p2 = 5;
}
}
static void skl_wrpll_context_init(struct skl_wrpll_context *ctx)
{
memset(ctx, 0, sizeof(*ctx));
ctx->min_deviation = UINT64_MAX;
}
static void skl_wrpll_params_populate(struct skl_wrpll_params *params,
uint64 afe_clock,
int ref_clock,
uint64 central_freq,
uint32 p0, uint32 p1, uint32 p2)
{
uint64 dco_freq;
switch (central_freq) {
case 9600000000ULL:
params->central_freq = 0;
break;
case 9000000000ULL:
params->central_freq = 1;
break;
case 8400000000ULL:
params->central_freq = 3;
}
switch (p0) {
case 1:
params->pdiv = 0;
break;
case 2:
params->pdiv = 1;
break;
case 3:
params->pdiv = 2;
break;
case 7:
params->pdiv = 4;
break;
default:
TRACE("%s: Incorrect PDiv\n", __func__);
}
switch (p2) {
case 5:
params->kdiv = 0;
break;
case 2:
params->kdiv = 1;
break;
case 3:
params->kdiv = 2;
break;
case 1:
params->kdiv = 3;
break;
default:
TRACE("%s: Incorrect KDiv\n", __func__);
}
params->qdiv_ratio = p1;
params->qdiv_mode = (params->qdiv_ratio == 1) ? 0 : 1;
dco_freq = p0 * p1 * p2 * afe_clock;
TRACE("%s: AFE frequency %" B_PRIu64 "Hz\n", __func__, afe_clock);
TRACE("%s: p0: %" B_PRIu32 ", p1: %" B_PRIu32 ", p2: %" B_PRIu32 "\n",
__func__, p0,p1,p2);
TRACE("%s: DCO frequency %" B_PRIu64 "Hz\n", __func__, dco_freq);
params->dco_integer = (uint64)dco_freq / ((uint64)ref_clock * 1000);
params->dco_fraction = (
(uint64)dco_freq / ((uint64)ref_clock / 1000) -
(uint64)params->dco_integer * 1000000) * 0x8000 /
1000000;
TRACE("%s: Reference clock: %gMhz\n", __func__, ref_clock / 1000.0f);
TRACE("%s: DCO integer %" B_PRIu32 "\n", __func__, params->dco_integer);
TRACE("%s: DCO fraction 0x%" B_PRIx32 "\n", __func__, params->dco_fraction);
}
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
bool
skl_ddi_calculate_wrpll(int clock ,
int ref_clock,
struct skl_wrpll_params *wrpll_params)
{
uint64 afe_clock = (uint64) clock * 5;
uint64 dco_central_freq[3] = { 8400000000ULL,
9000000000ULL,
9600000000ULL };
static const int even_dividers[] = { 4, 6, 8, 10, 12, 14, 16, 18, 20,
24, 28, 30, 32, 36, 40, 42, 44,
48, 52, 54, 56, 60, 64, 66, 68,
70, 72, 76, 78, 80, 84, 88, 90,
92, 96, 98 };
static const int odd_dividers[] = { 3, 5, 7, 9, 15, 21, 35 };
static const struct {
const int *list;
unsigned int n_dividers;
} dividers[] = {
{ even_dividers, ARRAY_SIZE(even_dividers) },
{ odd_dividers, ARRAY_SIZE(odd_dividers) },
};
struct skl_wrpll_context ctx;
unsigned int dco, d, i;
unsigned int p0, p1, p2;
skl_wrpll_context_init(&ctx);
for (d = 0; d < ARRAY_SIZE(dividers); d++) {
for (dco = 0; dco < ARRAY_SIZE(dco_central_freq); dco++) {
for (i = 0; i < dividers[d].n_dividers; i++) {
unsigned int p = dividers[d].list[i];
uint64 dco_freq = p * afe_clock;
skl_wrpll_try_divider(&ctx,
dco_central_freq[dco],
dco_freq,
p);
if (ctx.min_deviation == 0)
goto skip_remaining_dividers;
}
}
skip_remaining_dividers:
if (d == 0 && ctx.p)
break;
}
if (!ctx.p) {
TRACE("%s: No valid divider found for %dHz\n", __func__, clock);
return false;
}
TRACE("%s: Full divider (p) found is %d\n", __func__, ctx.p);
p0 = p1 = p2 = 0;
skl_wrpll_get_multipliers(ctx.p, &p0, &p1, &p2);
skl_wrpll_params_populate(wrpll_params, afe_clock, ref_clock,
ctx.central_freq, p0, p1, p2);
return true;
}