blob: c7b7bd723123969e81ad01dcc9e2049866ff5da3 [file] [log] [blame]
/*
* Copyright © 2017 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define U32_MAX ((uint32_t)~0ULL)
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
static inline uint64_t div_u64(uint64_t dividend, uint32_t divisor)
{
return dividend / divisor;
}
struct skl_wrpll_params {
uint32_t dco_fraction;
uint32_t dco_integer;
uint32_t qdiv_ratio;
uint32_t qdiv_mode;
uint32_t kdiv;
uint32_t pdiv;
/* for this test code only */
unsigned int ref_clock;
} __attribute__((packed));
static void dump_params(const char *name, struct skl_wrpll_params *params)
{
printf("%s:\n", name);
printf("Pdiv: %d\n", params->pdiv);
printf("Qdiv: %d\n", params->qdiv_ratio);
printf("Kdiv: %d\n", params->kdiv);
printf("qdiv mode: %d\n", params->qdiv_mode);
printf("dco integer: %d\n", params->dco_integer);
printf("dco fraction: %d\n", params->dco_fraction);
}
static void compare_params(unsigned int clock,
const char *name1, struct skl_wrpll_params *p1,
const char *name2, struct skl_wrpll_params *p2)
{
if (memcmp(p1, p2, sizeof(struct skl_wrpll_params)) == 0)
return;
printf("=======================================\n");
printf("Difference with clock: %10.6f MHz\n", clock/1000000.0);
printf("Reference clock: %10.6f MHz\n\n", p1->ref_clock/1000.0);
dump_params(name1, p1);
printf("\n");
dump_params(name2, p2);
printf("=======================================\n");
}
static void cnl_wrpll_params_populate(struct skl_wrpll_params *params,
uint32_t dco_freq, uint32_t ref_freq,
uint32_t pdiv, uint32_t qdiv,
uint32_t kdiv)
{
uint32_t dco;
params->qdiv_ratio = qdiv;
params->qdiv_mode = (qdiv == 1) ? 0 : 1;
params->pdiv = pdiv;
params->kdiv = kdiv;
if (kdiv != 2 && qdiv != 1)
printf("kdiv != 2 and qdiv != 1\n");
dco = div_u64((uint64_t)dco_freq << 15, ref_freq);
params->dco_integer = dco >> 15;
params->dco_fraction = dco & 0x7fff;
}
static void cnl_wrpll_get_multipliers(int bestdiv,
int *pdiv,
int *qdiv,
int *kdiv)
{
/* even dividers */
if (bestdiv % 2 == 0) {
if (bestdiv == 2) {
*pdiv = 2;
*qdiv = 1;
*kdiv = 1;
} else if (bestdiv % 4 == 0) {
*pdiv = 2;
*qdiv = bestdiv / 4;
*kdiv = 2;
} else if (bestdiv % 6 == 0) {
*pdiv = 3;
*qdiv = bestdiv / 6;
*kdiv = 2;
} else if (bestdiv % 5 == 0) {
*pdiv = 5;
*qdiv = bestdiv / 10;
*kdiv = 2;
} else if (bestdiv % 14 == 0) {
*pdiv = 7;
*qdiv = bestdiv / 14;
*kdiv = 2;
}
} else {
if (bestdiv == 3 || bestdiv == 5 || bestdiv == 7) {
*pdiv = bestdiv;
*qdiv = 1;
*kdiv = 1;
} else { /* 9, 15, 21 */
*pdiv = bestdiv / 3;
*qdiv = 1;
*kdiv = 3;
}
}
}
static bool
cnl_ddi_calculate_wrpll1(int clock /* in Hz */,
struct skl_wrpll_params *params)
{
double afe_clock = (clock/1000000.0) * 5; /* clocks in MHz */
double dco_min = 7998;
double dco_max = 10000;
double dco_mid = (dco_min + dco_max) / 2;
static const int dividers[] = { 2, 4, 6, 8, 10, 12, 14, 16,
18, 20, 24, 28, 30, 32, 36, 40,
42, 44, 48, 50, 52, 54, 56, 60,
64, 66, 68, 70, 72, 76, 78, 80,
84, 88, 90, 92, 96, 98, 100, 102,
3, 5, 7, 9, 15, 21 };
double dco, dco_centrality = 0;
double best_dco_centrality = 999999;
int d, best_div = 0, pdiv = 0, qdiv = 0, kdiv = 0;
double ref_clock = params->ref_clock/1000.0; /* MHz */
uint32_t dco_int, dco_frac;
for (d = 0; d < ARRAY_SIZE(dividers); d++) {
dco = afe_clock * dividers[d];
if ((dco <= dco_max) && (dco >= dco_min)) {
dco_centrality = fabs(dco - dco_mid);
if (dco_centrality < best_dco_centrality) {
best_dco_centrality = dco_centrality;
best_div = dividers[d];
dco_int = (uint32_t)(dco/ref_clock);
dco_frac = round((dco/ref_clock - dco_int) * (1<<15));
}
}
}
if (best_div != 0) {
cnl_wrpll_get_multipliers(best_div, &pdiv, &qdiv, &kdiv);
params->qdiv_ratio = qdiv;
params->qdiv_mode = (qdiv == 1) ? 0 : 1;
params->pdiv = pdiv;
params->kdiv = kdiv;
params->dco_integer = dco_int;
params->dco_fraction = dco_frac;
} else {
return false;
}
return true;
}
static bool
cnl_ddi_calculate_wrpll2(int clock,
struct skl_wrpll_params *params)
{
uint32_t afe_clock = clock * 5 / 1000; /* clock in kHz */
uint32_t dco_min = 7998000;
uint32_t dco_max = 10000000;
uint32_t dco_mid = (dco_min + dco_max) / 2;
static const int dividers[] = { 2, 4, 6, 8, 10, 12, 14, 16,
18, 20, 24, 28, 30, 32, 36, 40,
42, 44, 48, 50, 52, 54, 56, 60,
64, 66, 68, 70, 72, 76, 78, 80,
84, 88, 90, 92, 96, 98, 100, 102,
3, 5, 7, 9, 15, 21 };
uint32_t dco, best_dco = 0, dco_centrality = 0;
uint32_t best_dco_centrality = U32_MAX; /* Spec meaning of 999999 MHz */
int d, best_div = 0, pdiv = 0, qdiv = 0, kdiv = 0;
uint32_t ref_clock = params->ref_clock;
for (d = 0; d < ARRAY_SIZE(dividers); d++) {
dco = afe_clock * dividers[d];
if ((dco <= dco_max) && (dco >= dco_min)) {
dco_centrality = abs(dco - dco_mid);
if (dco_centrality < best_dco_centrality) {
best_dco_centrality = dco_centrality;
best_div = dividers[d];
best_dco = dco;
}
}
}
if (best_div == 0)
return false;
cnl_wrpll_get_multipliers(best_div, &pdiv, &qdiv, &kdiv);
cnl_wrpll_params_populate(params, best_dco, ref_clock,
pdiv, qdiv, kdiv);
return true;
}
static void test_multipliers(unsigned int clock)
{
uint64_t afe_clock = clock * 5 / 1000; /* clocks in kHz */
unsigned int dco_min = 7998000;
unsigned int dco_max = 10000000;
unsigned int dco_mid = (dco_min + dco_max) / 2;
static const int dividerlist[] = { 2, 4, 6, 8, 10, 12, 14, 16,
18, 20, 24, 28, 30, 32, 36, 40,
42, 44, 48, 50, 52, 54, 56, 60,
64, 66, 68, 70, 72, 76, 78, 80,
84, 88, 90, 92, 96, 98, 100, 102,
3, 5, 7, 9, 15, 21 };
unsigned int dco, dco_centrality = 0;
unsigned int best_dco_centrality = U32_MAX;
int d, best_div = 0, pdiv = 0, qdiv = 0, kdiv = 0;
for (d = 0; d < ARRAY_SIZE(dividerlist); d++) {
dco = afe_clock * dividerlist[d];
if ((dco <= dco_max) && (dco >= dco_min)) {
dco_centrality = abs(dco - dco_mid);
if (dco_centrality < best_dco_centrality) {
best_dco_centrality = dco_centrality;
best_div = dividerlist[d];
}
}
if (best_div != 0) {
cnl_wrpll_get_multipliers(best_div, &pdiv, &qdiv, &kdiv);
if ((kdiv != 2) && (qdiv == 1))
continue;
else
break;
}
}
assert(pdiv);
assert(qdiv);
assert(kdiv);
if (kdiv != 2)
assert(qdiv == 1);
}
static const struct {
uint32_t clock; /* in Hz */
} modes[] = {
{19750000},
{23500000},
{23750000},
{25175000},
{25200000},
{26000000},
{27000000},
{27027000},
{27500000},
{28750000},
{29750000},
{30750000},
{31500000},
{35000000},
{35500000},
{36750000},
{37000000},
{37088000},
{37125000},
{37762500},
{37800000},
{38250000},
{40500000},
{40541000},
{40750000},
{41000000},
{41500000},
{42500000},
{45250000},
{46360000},
{46406000},
{46750000},
{49000000},
{50500000},
{52000000},
{54000000},
{54054000},
{54500000},
{55632000},
{55688000},
{56000000},
{56750000},
{58250000},
{58750000},
{59341000},
{59400000},
{60500000},
{62250000},
{63500000},
{64000000},
{65250000},
{65500000},
{66750000},
{67750000},
{68250000},
{69000000},
{72000000},
{74176000},
{74250000},
{74500000},
{75250000},
{76000000},
{79500000},
{81000000},
{81081000},
{82000000},
{83000000},
{84750000},
{85250000},
{85750000},
{88500000},
{89012000},
{89100000},
{91000000},
{92719800},
{92812500},
{94500000},
{95750000},
{97750000},
{99000000},
{99750000},
{100000000},
{100500000},
{101000000},
{101250000},
{102250000},
{107892000},
{108000000},
{108108000},
{109000000},
{110250000},
{110500000},
{111264000},
{111375000},
{112500000},
{117500000},
{119000000},
{119500000},
{121250000},
{121750000},
{125250000},
{125750000},
{127250000},
{130000000},
{130250000},
{131000000},
{131500000},
{132750000},
{135250000},
{138500000},
{138750000},
{141500000},
{146250000},
{148250000},
{148352000},
{148500000},
{154000000},
{155250000},
{155750000},
{156000000},
{158250000},
{159500000},
{161000000},
{162000000},
{162162000},
{162500000},
{169500000},
{172750000},
{173000000},
{175000000},
{178500000},
{179500000},
{184750000},
{185440000},
{185625000},
{187000000},
{192250000},
{193250000},
{197750000},
{198500000},
{204750000},
{207500000},
{209250000},
{213750000},
{214750000},
{216000000},
{218750000},
{219000000},
{220750000},
{222525000},
{222750000},
{227000000},
{230250000},
{233500000},
{235000000},
{238000000},
{241500000},
{243000000},
{245250000},
{247750000},
{253250000},
{256250000},
{262500000},
{267250000},
{268500000},
{270000000},
{272500000},
{273750000},
{280750000},
{281250000},
{286000000},
{291750000},
{296703000},
{297000000},
{298000000},
{303750000},
{322250000},
{324000000},
{337750000},
{370878750},
{371250000},
{373250000},
{414500000},
{432000000},
{445054500},
{445500000},
{497750000},
{533250000},
{540000000},
{592500000},
{594000000},
{648000000},
{810000000},
};
static void test_run(unsigned int ref_clock)
{
unsigned int m;
struct skl_wrpll_params params[2];
for (m = 0; m < ARRAY_SIZE(modes); m++) {
int clock = modes[m].clock;
bool skip = false;
params[0].ref_clock = params[1].ref_clock = ref_clock;
if (!cnl_ddi_calculate_wrpll1(clock, &params[0])) {
fprintf(stderr, "Reference: Couldn't compute divider for %dHz, reference %dHz\n",
clock, params[0].ref_clock*1000);
skip = true;
}
if (!skip) {
if (!cnl_ddi_calculate_wrpll2(clock, &params[1])) {
fprintf(stderr, "i915 implementation: Couldn't compute divider for %dHz, reference %dHz\n",
clock, params[1].ref_clock*1000);
}
compare_params(clock, "Reference", &params[0],
"i915 implementation", &params[1]);
}
}
}
int main(int argc, char **argv)
{
unsigned int m;
unsigned int f;
unsigned int ref_clocks[] = {19200, 24000}; /* in kHz */
for (m = 0; m < ARRAY_SIZE(modes); m++)
test_multipliers(modes[m].clock);
for (f = 0; f < ARRAY_SIZE(ref_clocks); f++) {
printf("=== Testing with ref clock %d kHz\n", ref_clocks[f]);
test_run(ref_clocks[f]);
}
return 0;
}