# HG changeset patch # User Paul Boddie # Date 1695412594 -7200 # Node ID 40a23a1e92f80b96bdfaa74cc16e6b01142cff9a # Parent 80a1c6ce717bab95609da453810ce3530c11b760 Added some support for setting the frequency of different clocks and PLLs. diff -r 80a1c6ce717b -r 40a23a1e92f8 pkg/devices/lib/cpm/include/cpm-common.h --- a/pkg/devices/lib/cpm/include/cpm-common.h Fri Sep 22 21:55:54 2023 +0200 +++ b/pkg/devices/lib/cpm/include/cpm-common.h Fri Sep 22 21:56:34 2023 +0200 @@ -81,7 +81,9 @@ uint32_t get_field(Cpm_regs ®s); void set_field(Cpm_regs ®s, uint32_t value); + bool is_defined() { return defined; } + uint32_t get_limit() { return mask; } // Undefined field object. @@ -231,6 +233,9 @@ int pll_bypassed(Cpm_regs ®s); + void pll_bypass(Cpm_regs ®s); + void pll_engage(Cpm_regs ®s); + void wait_busy(Cpm_regs ®s); int have_clock(Cpm_regs ®s); void start_clock(Cpm_regs ®s); @@ -249,6 +254,7 @@ // Output frequency. virtual uint32_t get_frequency(Cpm_regs ®s, uint32_t source_frequency) = 0; + virtual int set_frequency(Cpm_regs ®s, uint32_t source_frequency, uint32_t frequency) = 0; // Other operations. @@ -284,6 +290,7 @@ // Output frequency. uint32_t get_frequency(Cpm_regs ®s, uint32_t source_frequency); + int set_frequency(Cpm_regs ®s, uint32_t source_frequency, uint32_t frequency); // Other operations. @@ -319,6 +326,7 @@ // Output frequency. uint32_t get_frequency(Cpm_regs ®s, uint32_t source_frequency); + int set_frequency(Cpm_regs ®s, uint32_t source_frequency, uint32_t frequency); // Other operations. @@ -351,6 +359,7 @@ // Output frequency. uint32_t get_frequency(Cpm_regs ®s, uint32_t source_frequency); + int set_frequency(Cpm_regs ®s, uint32_t source_frequency, uint32_t frequency); // Other operations. @@ -493,6 +502,7 @@ // Output frequency. uint32_t get_frequency(Cpm_regs ®s); + virtual int set_frequency(Cpm_regs ®s, uint32_t frequency); }; @@ -520,6 +530,7 @@ // Output frequency. uint32_t get_frequency(Cpm_regs ®s); + int set_frequency(Cpm_regs ®s, uint32_t frequency); }; diff -r 80a1c6ce717b -r 40a23a1e92f8 pkg/devices/lib/cpm/include/cpm-x1600.h --- a/pkg/devices/lib/cpm/include/cpm-x1600.h Fri Sep 22 21:55:54 2023 +0200 +++ b/pkg/devices/lib/cpm/include/cpm-x1600.h Fri Sep 22 21:56:34 2023 +0200 @@ -72,7 +72,7 @@ // Output clock frequencies. uint32_t get_frequency(enum Clock_identifiers clock); - void set_frequency(enum Clock_identifiers clock, uint32_t frequency); + int set_frequency(enum Clock_identifiers clock, uint32_t frequency); }; #endif /* __cplusplus */ @@ -104,6 +104,6 @@ uint32_t x1600_cpm_get_source_frequency(void *cpm, enum Clock_identifiers clock); uint32_t x1600_cpm_get_frequency(void *cpm, enum Clock_identifiers clock); -void x1600_cpm_set_frequency(void *cpm, enum Clock_identifiers clock, uint32_t frequency); +int x1600_cpm_set_frequency(void *cpm, enum Clock_identifiers clock, uint32_t frequency); EXTERN_C_END diff -r 80a1c6ce717b -r 40a23a1e92f8 pkg/devices/lib/cpm/src/common.cc --- a/pkg/devices/lib/cpm/src/common.cc Fri Sep 22 21:55:54 2023 +0200 +++ b/pkg/devices/lib/cpm/src/common.cc Fri Sep 22 21:56:34 2023 +0200 @@ -226,6 +226,18 @@ return _bypass.get_field(regs); } +void +Control_pll::pll_bypass(Cpm_regs ®s) +{ + _bypass.set_field(regs, 1); +} + +void +Control_pll::pll_engage(Cpm_regs ®s) +{ + _bypass.set_field(regs, 0); +} + // Clock control. int @@ -290,6 +302,13 @@ } int +Divider::set_frequency(Cpm_regs ®s, uint32_t source_frequency, uint32_t frequency) +{ + set_divider(regs, (uint32_t) round((double) source_frequency / (double) frequency)); + return 1; +} + +int Divider::get_parameters(Cpm_regs ®s, uint32_t parameters[]) { parameters[0] = get_divider(regs); @@ -310,41 +329,97 @@ -// Feedback (13-bit) multiplier. +// Common divider functionality. + +static int is_integer(double x) +{ + double target = round(x) * 1000; + double rounded = floor(x * 1000); + + return (target - 100 < rounded) && (rounded < target + 100); +} + +static double getscale_part(double x) +{ + double part = x - floor(x); + + if (part > 0.5) + return 1 / (1 - part); + else if (part > 0) + return 1 / part; + else + return 1; +} + +static double getscale(double x) +{ + double scale = getscale_part(x); + + if (is_integer(scale)) + return scale; + else + return scale * getscale(scale); +} + +static void get_divider_operands(double frequency, double source_frequency, + double *multiplier, double *divider) +{ + double ratio = frequency / source_frequency; + double scale = getscale(ratio); + + *multiplier = scale * ratio; + *divider = scale; +} + +static void reduce_divider_operands(uint32_t *m, uint32_t *n, uint32_t m_limit, + uint32_t n_limit) +{ + while ((*m > m_limit) && (*n > n_limit) && (*m > 1) && (*n > 1)) + { + *m >>= 1; + *n >>= 1; + } +} + +#define zero_as_one(X) ((X) ? (X) : 1) + + + +// Feedback multiplier. uint32_t Divider_pll::get_multiplier(Cpm_regs ®s) { - return _multiplier.get_field(regs) + 1; + return zero_as_one(_multiplier.get_field(regs)); } void Divider_pll::set_multiplier(Cpm_regs ®s, uint32_t multiplier) { - _multiplier.set_field(regs, multiplier - 1); + _multiplier.set_field(regs, multiplier); } -// Input (6-bit) divider. +// Input divider. uint32_t Divider_pll::get_input_divider(Cpm_regs ®s) { - return _input_divider.get_field(regs) + 1; + return zero_as_one(_input_divider.get_field(regs)); } void Divider_pll::set_input_divider(Cpm_regs ®s, uint32_t divider) { - _input_divider.set_field(regs, divider - 1); + _input_divider.set_field(regs, divider); } -// Output (dual 3-bit) dividers. +// Output dividers. uint32_t Divider_pll::get_output_divider(Cpm_regs ®s) { - uint32_t d0 = _output_divider0.get_field(regs); - uint32_t d1 = _output_divider1.get_field(regs); + uint32_t d0 = zero_as_one(_output_divider0.get_field(regs)); + uint32_t d1 = zero_as_one(_output_divider1.get_field(regs)); return d0 * d1; } @@ -355,8 +430,21 @@ // Assert 1 as a minimum. // Divider 0 must be less than or equal to divider 1. - uint32_t d0 = (uint32_t) floor(sqrt(divider ? divider : 1)); - uint32_t d1 = divider / d0; + uint32_t d0, d1; + + if (!divider) + divider = 1; + + if (divider < _output_divider1.get_limit()) + { + d0 = 1; + d1 = divider; + } + else + { + d0 = (uint32_t) floor(sqrt(divider)); + d1 = divider / d0; + } _output_divider0.set_field(regs, d0); _output_divider1.set_field(regs, d1); @@ -370,6 +458,66 @@ } int +Divider_pll::set_frequency(Cpm_regs ®s, uint32_t source_frequency, uint32_t frequency) +{ + double intermediate_min = 600000000, intermediate_max = 2400000000; + double intermediate_multiplier, intermediate_input_divider; + uint32_t output_min, output_max, output0, output1; + uint32_t multiplier, input_divider, output_divider; + + // Distribute the divider across the input and output divider. For the X1600, + // the multiplier and input divider should collectively deliver a frequency in + // the range 600-2400 MHz. + + output_min = (uint32_t) ceil(intermediate_min / frequency); + output_max = (uint32_t) floor(intermediate_max / frequency); + + output_divider = output_min; + + while (output_divider <= output_max) + { + // Test divider constraints. + + output0 = (uint32_t) floor(sqrt(output_divider)); + output1 = (uint32_t) floor(output_divider / output0); + + if ((output0 * output1 == output_divider) && + (output0 <= _output_divider0.get_limit()) && + (output1 <= _output_divider1.get_limit())) + { + // Calculate the other parameters. + + uint32_t intermediate_frequency = frequency * output_divider; + + get_divider_operands(intermediate_frequency, source_frequency, + &intermediate_multiplier, &intermediate_input_divider); + + multiplier = (uint32_t) round(intermediate_multiplier); + input_divider = (uint32_t) round(intermediate_input_divider); + + uint32_t multiplier_limit = _multiplier.get_limit(); + uint32_t input_divider_limit = _input_divider.get_limit(); + + reduce_divider_operands(&multiplier, &input_divider, + multiplier_limit, input_divider_limit); + + if ((multiplier <= multiplier_limit) && (input_divider <= input_divider_limit)) + { + set_multiplier(regs, multiplier); + set_input_divider(regs, input_divider); + set_output_divider(regs, output_divider); + + return 1; + } + } + + output_divider++; + } + + return 0; +} + +int Divider_pll::get_parameters(Cpm_regs ®s, uint32_t parameters[]) { parameters[0] = get_multiplier(regs); @@ -418,11 +566,46 @@ uint32_t Divider_i2s::get_frequency(Cpm_regs ®s, uint32_t source_frequency) { + /* NOTE: Assuming that this is the formula, given that the manual does not + really describe how D is used. */ + return (source_frequency * get_multiplier(regs)) / (get_divider_N(regs) * get_divider_D(regs)); } int +Divider_i2s::set_frequency(Cpm_regs ®s, uint32_t source_frequency, uint32_t frequency) +{ + double m, n; + + get_divider_operands(frequency, source_frequency, &m, &n); + + uint32_t multiplier = (uint32_t) round(m); + uint32_t divider = (uint32_t) round(n); + + reduce_divider_operands(&multiplier, ÷r, + _multiplier.get_limit(), + _divider_N.get_limit()); + + // Test for operand within limits and the N >= 2M constraint. + + if ((multiplier <= _multiplier.get_limit()) && (divider <= _divider_N.get_limit()) && + (divider >= 2 * multiplier)) + { + /* NOTE: Setting D to 1. Even though it seems that D might also be used, + it does not seem necessary in practice, and the documentation is + unclear about its use. */ + + uint32_t parameters[] = {multiplier, divider, 1}; + + set_parameters(regs, 3, parameters); + return 1; + } + + return 0; +} + +int Divider_i2s::get_parameters(Cpm_regs ®s, uint32_t parameters[]) { parameters[0] = get_multiplier(regs); @@ -446,7 +629,7 @@ } else if (num_parameters > 1) { - // Test for N < 2M. + // Require N >= 2M, returning otherwise. if (parameters[1] < 2 * parameters[0]) return 0; @@ -635,6 +818,17 @@ } int +Clock_divided_base::set_frequency(Cpm_regs ®s, uint32_t frequency) +{ + _get_control().change_enable(regs); + int result = _get_divider().set_frequency(regs, get_source_frequency(regs), frequency); + _get_control().wait_busy(regs); + _get_control().change_disable(regs); + + return result; +} + +int Clock_divided_base::get_parameters(Cpm_regs ®s, uint32_t parameters[]) { return _get_divider().get_parameters(regs, parameters); @@ -667,3 +861,12 @@ else return get_source_frequency(regs); } + +int +Pll::set_frequency(Cpm_regs ®s, uint32_t frequency) +{ + int result = Clock_divided_base::set_frequency(regs, frequency); + _control.pll_engage(regs); + + return result; +} diff -r 80a1c6ce717b -r 40a23a1e92f8 pkg/devices/lib/cpm/src/x1600.cc --- a/pkg/devices/lib/cpm/src/x1600.cc Fri Sep 22 21:55:54 2023 +0200 +++ b/pkg/devices/lib/cpm/src/x1600.cc Fri Sep 22 21:56:34 2023 +0200 @@ -219,9 +219,9 @@ Pll_bypass_E (Pll_control_E, 1, 26), // EPLL_BP Pll_bypass_M (Pll_control_M, 1, 28), // MPLL_BP - Pll_multiplier_A (Pll_control_A, 0x1fff, 20), // APLLM - Pll_multiplier_E (Pll_control_E, 0x1fff, 20), // EPLLM - Pll_multiplier_M (Pll_control_M, 0x1fff, 20), // MPLLM + Pll_multiplier_A (Pll_control_A, 0xfff, 20), // APLLM + Pll_multiplier_E (Pll_control_E, 0x3f, 20), // EPLLM (observed) + Pll_multiplier_M (Pll_control_M, 0xfff, 20), // MPLLM Pll_input_division_A (Pll_control_A, 0x3f, 14), // APLLN Pll_input_division_E (Pll_control_E, 0x3f, 14), // EPLLN @@ -564,36 +564,15 @@ return clocks[clock]->get_frequency(_cpm_regs); } -void +int Cpm_x1600_chip::set_frequency(enum Clock_identifiers clock, uint32_t frequency) { - switch (clock) - { - // The pixel frequency is based on the selected clock source (SCLK_A, MPLL or - // EPLL). - - case Clock_lcd_pixel: - { - - // Switch to the MPLL and attempt to set the divider. - - Clock_divided_base *lcd = dynamic_cast(clocks[Clock_lcd_pixel]); - Clock_base *pll = clocks[Clock_pll_M]; + Clock_divided_base *clk = dynamic_cast(clocks[clock]); - if (lcd != NULL) - { - uint32_t parameters[] = {lcd->get_source_frequency(_cpm_regs) / frequency}; - - lcd->set_source(_cpm_regs, Source_mME_pll_M); - pll->start_clock(_cpm_regs); - lcd->set_parameters(_cpm_regs, 1, parameters); - } - break; - } - - default: - break; - } + if (clk != NULL) + return clk->set_frequency(_cpm_regs, frequency); + else + return 0; } @@ -681,8 +660,8 @@ return static_cast(cpm)->get_frequency(clock); } -void +int x1600_cpm_set_frequency(void *cpm, enum Clock_identifiers clock, uint32_t frequency) { - static_cast(cpm)->set_frequency(clock, frequency); + return static_cast(cpm)->set_frequency(clock, frequency); }