1# Copyright 2020 Cypress Semiconductor Corporation
2# SPDX-License-Identifier: Apache-2.0
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16# From https://wiki.tcl-lang.org/page/constants
17proc const {name value} {
18    uplevel 1 [list set $name $value]
19    uplevel 1 [list trace var $name w {error constant} ]
20}
21
22const ARG_MODE_IDX 0
23
24const ARG_AUTO_IDX_SRC_FREQ 1
25const ARG_AUTO_IDX_TARGET_FREQ 2
26const ARG_AUTO_IDX_WCO_SRC 3
27const ARG_AUTO_IDX_SRC_ACCURACY 4
28
29const ARG_MANUAL_IDX_SRC_FREQ 1
30const ARG_MANUAL_IDX_MULT 2
31const ARG_MANUAL_IDX_DIV 3
32const ARG_MANUAL_IDX_LOCK_TOLERANCE 4
33const ARG_MANUAL_IDX_EN_OUT_DIV 5
34const ARG_MANUAL_IDX_WCO_SRC 6
35const ARG_MANUAL_IDX_SRC_ACCURACY 7
36
37const MIN_CCO_OUTPUT_FREQ 48
38const MIN_OUTPUT_FREQ [expr {$::MIN_CCO_OUTPUT_FREQ / 2}]
39const MAX_OUTPUT_FREQ 100
40const SRC_CCO_FREQ_RATIO 2.2
41
42const OUTPUT_DIV_ENABLED 1
43
44const CCO_RANGE [list "CY_SYSCLK_FLL_CCO_RANGE0" "CY_SYSCLK_FLL_CCO_RANGE1" "CY_SYSCLK_FLL_CCO_RANGE2" \
45                 "CY_SYSCLK_FLL_CCO_RANGE3" "CY_SYSCLK_FLL_CCO_RANGE4"]
46const TRIM_STEPS [list 0.0011034 0.001102 0.0011 0.0011 0.00117062]
47const F_MARGIN [list 43600000.0 58100000.0 77200000.0 103000000.0 132000000.0]
48
49const CONFIG_OUTPUT_MODE "CY_SYSCLK_FLLPLL_OUTPUT_OUTPUT"
50
51# igain and pgain bitfield values correspond to: 1/256, 1/128, ..., 4, 8
52const IGAINS_PGAINS [list 0.00390625  0.0078125  0.015625  0.03125  0.0625  0.125 \
53                          0.25        0.5        1.0       2.0      4.0     8.0]
54
55const SUCCESS 0
56const ERROR_ARG_COUNT 1
57const ERROR_ID 2
58const ERROR_ARG_VALUE 3
59
60set channelName stdout
61
62if {[chan names ModusToolbox] eq "ModusToolbox"} {
63    set channelName ModusToolbox
64}
65
66proc solve_fll {} {
67    if {$::argc < $::ARG_MODE_IDX + 1} {
68        print_arg_count_error
69        return $::ERROR_ARG_COUNT
70    }
71    if {[string compare -nocase [lindex $::argv $::ARG_MODE_IDX] "Auto"] == 0} {
72        return [parse_and_run_auto]
73    }
74    if {[string compare -nocase [lindex $::argv $::ARG_MODE_IDX] "Manual"] == 0} {
75        return [parse_and_run_manual]
76    }
77    return $::ERROR_ARG_VALUE
78}
79
80proc parse_and_run_auto {} {
81    if {$::argc != $::ARG_AUTO_IDX_SRC_ACCURACY + 1} {
82        print_arg_count_error
83        return $::ERROR_ARG_COUNT
84    }
85    set srcFreqMHz [lindex $::argv $::ARG_AUTO_IDX_SRC_FREQ]
86    set targetFreqMHz [lindex $::argv $::ARG_AUTO_IDX_TARGET_FREQ]
87    set wcoIsSource [string is true [lindex $::argv $::ARG_AUTO_IDX_WCO_SRC]]
88    set srcAccuracyPercent [lindex $::argv $::ARG_AUTO_IDX_SRC_ACCURACY]
89    if {![string is double $srcFreqMHz] || ![string is double $targetFreqMHz] || ![string is double $srcAccuracyPercent]} {
90        error "Unable to parse argument values: either $srcFreqMHz or $targetFreqMHz or $srcAccuracyPercent is not a floating-point number."
91        return $::ERROR_ARG_VALUE
92    }
93    set srcFreqMHz [expr {double($srcFreqMHz)}]
94    set targetFreqMHz [expr {double($targetFreqMHz)}]
95    set srcAccuracyPercent [expr {double($srcAccuracyPercent)}]
96    set retVal [verify_desired_frequency $srcFreqMHz $targetFreqMHz $::OUTPUT_DIV_ENABLED]
97    if {$retVal != $::SUCCESS} {
98        return $retVal
99    }
100    return [solve_auto $srcFreqMHz $targetFreqMHz $wcoIsSource [expr {abs($srcAccuracyPercent / 100.0)}]]
101}
102
103proc parse_and_run_manual {} {
104    if {$::argc != $::ARG_MANUAL_IDX_SRC_ACCURACY + 1} {
105        print_arg_count_error
106        return $::ERROR_ARG_COUNT
107    }
108    set srcFreqMHz [lindex $::argv $::ARG_MANUAL_IDX_SRC_FREQ]
109    set mult [lindex $::argv $::ARG_MANUAL_IDX_MULT]
110    set div [lindex $::argv $::ARG_MANUAL_IDX_DIV]
111    set lockTol [lindex $::argv $::ARG_MANUAL_IDX_LOCK_TOLERANCE]
112	set enOutDiv [lindex $::argv $::ARG_MANUAL_IDX_EN_OUT_DIV]
113    set wcoIsSource [string is true [lindex $::argv $::ARG_MANUAL_IDX_WCO_SRC]]
114    set srcAccuracyPercent [lindex $::argv $::ARG_MANUAL_IDX_SRC_ACCURACY]
115    if {![string is double $srcFreqMHz] || ![string is double $srcAccuracyPercent]} {
116        error "Unable to parse argument values: either $srcFreqMHz or $srcAccuracyPercent is not a floating-point number."
117        return $::ERROR_ARG_VALUE
118    }
119    if {![string is integer $mult] || ![string is integer $div] || ![string is integer $lockTol]} {
120        error "Unable to parse argument values: either $mult or $div or $lockTol is not an integer."
121        return $::ERROR_ARG_VALUE
122    }
123    set srcFreqMHz [expr {double($srcFreqMHz)}]
124    set srcAccuracyPercent [expr {double($srcAccuracyPercent)}]
125
126    set expectedFreq [expr {$srcFreqMHz * $mult / $div / ($::OUTPUT_DIV_ENABLED ? 2 : 1)}]
127    set retVal [verify_desired_frequency $srcFreqMHz $expectedFreq $::OUTPUT_DIV_ENABLED]
128    if {$retVal != $::SUCCESS} {
129        return $retVal
130    }
131    return [solve_manual $srcFreqMHz $mult $div $lockTol $enOutDiv $wcoIsSource [expr {abs($srcAccuracyPercent / 100.0)}]]
132}
133
134proc solve_auto {srcFreqMHz targetFreqMHz wcoIsSource srcAccuracy} {
135    # constants indexed by ccoIdx
136    # 1. Output division by 2 is always required.
137    set configEnableOutputDiv $::OUTPUT_DIV_ENABLED
138    # 2. Compute the target CCO frequency from the target output frequency and output division.
139    set ccoFreqMHz [expr {$targetFreqMHz * ($configEnableOutputDiv ? 2 : 1)}]
140    # 3. Compute the CCO range value from the CCO frequency.
141    set ccoIdx [get_cco_range_idx $ccoFreqMHz]
142    set configCcoRange [lindex $::CCO_RANGE $ccoIdx]
143    # 4. Compute the FLL reference divider value. refDiv is a constant if the WCO is the FLL source.
144    set configRefDiv [expr {$wcoIsSource ? 19 : int(ceil(($srcFreqMHz / $targetFreqMHz) * 250.0))}]
145    # 5. Compute the FLL multiplier value.
146    set configFllMult [expr {int(ceil($ccoFreqMHz / ($srcFreqMHz / $configRefDiv)))}]
147    # 6. Compute the lock tolerance.  We assume CCO accuracy is 0.25%.
148    set CCO_ACCURACY 0.0025
149    set configLockTolerance [expr {int(ceil($configFllMult * (1.5 * ((1.0 + $CCO_ACCURACY) / (1.0 - $srcAccuracy) - 1.0))))}]
150    # 7. Compute the CCO igain and pgain.
151    set kcco [expr {([lindex $::TRIM_STEPS $ccoIdx] * [lindex $::F_MARGIN $ccoIdx]) / 1000.0}]
152    set ki_p [expr {850.0 / ($kcco * ($configRefDiv / $srcFreqMHz))}]
153    # 7a. Find the largest IGAIN value that is less than or equal to ki_p.
154    set configIgain [expr {[llength $::IGAINS_PGAINS] - 1}]
155    while {([lindex $::IGAINS_PGAINS $configIgain] > $ki_p) && ($configIgain != 0)} {
156        incr configIgain -1
157    }
158    # 7b. Decrement igain if the WCO is the FLL source.
159    if {$wcoIsSource && ($configIgain > 0)} {
160        incr configIgain -1
161    }
162    # 7c. Find the largest PGAIN value that is less than or equal to ki_p - IGAINS_PGAINS[igain]
163    set configPgain [expr {[llength $::IGAINS_PGAINS] - 1}]
164    while {([lindex $::IGAINS_PGAINS $configPgain] > ($ki_p - [lindex $::IGAINS_PGAINS $configIgain]) && ($configPgain != 0))} {
165        incr configPgain -1
166    }
167    # 7d. Decrement pgain if the WCO is the FLL source.
168    if {$wcoIsSource && ($configPgain > 0)} {
169        incr configPgain -1
170    }
171    # 8. Compute the CCO_FREQ bits in CLK_FLL_CONFIG4 register.
172    set configCcoFreq [expr {int(floor(log($ccoFreqMHz * 1000000 / [lindex $::F_MARGIN $ccoIdx]) / log(1.0 + [lindex $::TRIM_STEPS $ccoIdx])))}]
173    # 9. Compute the settling count, using a 1-usec settling time.  Use a constant if the WCO is the FLL source.
174    set configSettlingCount [get_settling_count $wcoIsSource $srcFreqMHz $ccoFreqMHz $configRefDiv]
175    set accuracy [calculate_fll_accuracy $ccoFreqMHz $srcFreqMHz [lindex $::TRIM_STEPS $ccoIdx] $configRefDiv $configLockTolerance]
176    set accuracyString [format "%.04f" [expr {$accuracy * 100.0}]]
177    set enableOutputDiv [expr {$configEnableOutputDiv ? "true" : "false"}]
178
179    puts $::channelName "param:fllMult=$configFllMult"
180    puts $::channelName "param:refDiv=$configRefDiv"
181    puts $::channelName "param:ccoRange=$configCcoRange"
182    puts $::channelName "param:enableOutputDiv=$enableOutputDiv"
183    puts $::channelName "param:lockTolerance=$configLockTolerance"
184    puts $::channelName "param:igain=$configIgain"
185    puts $::channelName "param:pgain=$configPgain"
186    puts $::channelName "param:settlingCount=$configSettlingCount"
187    puts $::channelName "param:outputMode=$::CONFIG_OUTPUT_MODE"
188    puts $::channelName "param:cco_Freq=$configCcoFreq"
189    puts $::channelName "param:accuracy=$accuracyString"
190    return $::SUCCESS
191}
192
193proc solve_manual {srcFreqMHz configFllMult configRefDiv configLockTolerance configenOutDiv wcoIsSource srcAccuracy} {
194    # constants indexed by ccoIdx
195    # 1. Output division by 2 is always required.
196    set configEnableOutputDiv [expr {$configenOutDiv ? 1 : 0}]
197    set outputDiv [expr {$configEnableOutputDiv ? 2 : 1}]
198    set actualFreq [expr {(($srcFreqMHz * $configFllMult) / $configRefDiv) / $outputDiv}]
199    # 2. Compute the target CCO frequency from the target output frequency and output division.
200    set ccoFreqMHz [expr {$actualFreq * $outputDiv}]
201    # 3. Compute the CCO range value from the CCO frequency.
202    set ccoIdx [get_cco_range_idx $ccoFreqMHz]
203    set configCcoRange [lindex $::CCO_RANGE $ccoIdx]
204    # 7. Compute the CCO igain and pgain.
205    set kcco [expr {([lindex $::TRIM_STEPS $ccoIdx] * [lindex $::F_MARGIN $ccoIdx]) / 1000.0}]
206    set ki_p [expr {850.0 / ($kcco * ($configRefDiv / $srcFreqMHz))}]
207    # 7a. Find the largest IGAIN value that is less than or equal to ki_p.
208    set configIgain [expr {[llength $::IGAINS_PGAINS] - 1}]
209    while {([lindex $::IGAINS_PGAINS $configIgain] > $ki_p) && ($configIgain != 0)} {
210        incr configIgain -1
211    }
212    # 7b. Decrement igain if the WCO is the FLL source.
213    if {$wcoIsSource && ($configIgain > 0)} {
214        incr configIgain -1
215    }
216    # 7c. Find the largest PGAIN value that is less than or equal to ki_p - IGAINS_PGAINS[igain]
217    set configPgain [expr {[llength $::IGAINS_PGAINS] - 1}]
218    while {([lindex $::IGAINS_PGAINS $configPgain] > ($ki_p - [lindex $::IGAINS_PGAINS $configIgain]) && ($configPgain != 0))} {
219        incr configPgain -1
220    }
221    # 7d. Decrement pgain if the WCO is the FLL source.
222    if {$wcoIsSource && ($configPgain > 0)} {
223        incr configPgain -1
224    }
225    # 8. Compute the CCO_FREQ bits in CLK_FLL_CONFIG4 register.
226    set configCcoFreq [expr {int(floor(log($ccoFreqMHz * 1000000 / [lindex $::F_MARGIN $ccoIdx]) / log(1.0 + [lindex $::TRIM_STEPS $ccoIdx])))}]
227    # 9. Compute the settling count, using a 1-usec settling time.  Use a constant if the WCO is the FLL source.
228    set configSettlingCount [get_settling_count $wcoIsSource $srcFreqMHz $ccoFreqMHz $configRefDiv]
229    set accuracy [calculate_fll_accuracy $ccoFreqMHz $srcFreqMHz [lindex $::TRIM_STEPS $ccoIdx] $configRefDiv $configLockTolerance]
230    set accuracyString [format "%.04f" [expr {$accuracy * 100.0}]]
231    set enableOutputDiv [expr {$configEnableOutputDiv ? "true" : "false"}]
232
233    puts $::channelName "param:fllMult=$configFllMult"
234    puts $::channelName "param:refDiv=$configRefDiv"
235    puts $::channelName "param:ccoRange=$configCcoRange"
236    puts $::channelName "param:enableOutputDiv=$enableOutputDiv"
237    puts $::channelName "param:lockTolerance=$configLockTolerance"
238    puts $::channelName "param:igain=$configIgain"
239    puts $::channelName "param:pgain=$configPgain"
240    puts $::channelName "param:settlingCount=$configSettlingCount"
241    puts $::channelName "param:outputMode=$::CONFIG_OUTPUT_MODE"
242    puts $::channelName "param:cco_Freq=$configCcoFreq"
243    puts $::channelName "param:accuracy=$accuracyString"
244    return $::SUCCESS
245}
246
247proc get_settling_count {isWco srcFreqMHz ccoFreqMHz configRefDiv} {
248    set ttref [expr {$configRefDiv / ($srcFreqMHz * 1000.0)}]
249    set testval [expr {6.0 / ($ccoFreqMHz * 1000.0)}]
250    set divval [expr {ceil($srcFreqMHz)}]
251    set altval [expr {ceil(($divval / $ttref) + 1.0)}]
252    return [expr {int($isWco ? 200 : (($ttref > $testval) ? $divval : (($divval > $altval) ? $divval : $altval)))}]
253}
254
255proc get_cco_range_idx {ccoFreqMHz} {
256    if {$ccoFreqMHz >= 150.3392} {
257        return 4
258    } elseif {$ccoFreqMHz >= 113.00938} {
259        return 3
260    } elseif {$ccoFreqMHz >= 94.9487} {
261        return 2
262    } elseif {$ccoFreqMHz >= 63.8556} {
263        return 1
264    } else {
265        return 0
266    }
267}
268
269proc calculate_fll_accuracy {targetCcoFreq sourceRefFreq trimStep refDiv lockTolerance} {
270    set measure [expr {(1.0 / $refDiv) * ($sourceRefFreq / $targetCcoFreq)}]
271    set ss_2 [expr {$trimStep / 2.0}]
272    set fllPrecision [expr {($measure > $ss_2) ? $measure : $ss_2}]
273    return [expr {$fllPrecision * ($lockTolerance + 2)}]
274}
275
276proc verify_desired_frequency {sourceFreq targetFreq outputDiv} {
277    set ratio [expr {$outputDiv ? ($::SRC_CCO_FREQ_RATIO / 2) : $::SRC_CCO_FREQ_RATIO}]
278    if {$targetFreq < $::MIN_OUTPUT_FREQ || $::MAX_OUTPUT_FREQ < $targetFreq} {
279        error "Invalid FLL target frequency '$targetFreq'. Must be within the range $::MIN_OUTPUT_FREQ-$::MAX_OUTPUT_FREQ."
280        return $::ERROR_ARG_VALUE
281    }
282    if {($sourceFreq * $ratio) >= $targetFreq} {
283        error "Invalid FLL source/target frequency ratio.  Target must be at least $ratio faster than the source."
284        return $::ERROR_ARG_VALUE
285    }
286    return $::SUCCESS
287}
288
289proc print_arg_count_error {} {
290    error "FLL Solver requires 5 arguments in automatic mode:
291\t\"auto\" automatic - Use automatic solver based on desired frequency
292\tdouble srcFreqMHz - Source clock frequency for the FLL in MHz.
293\tdouble targetFreqMHz - Output frequency of the FLL in MHz.
294\tboolean wcoIsSource - Is the WCO sourcing the FLL?
295\tdouble sourceAccuracy - FLL reference clock accuracy (%)?
296FLL Solver requires 7 arguments in manual mode:
297\t\"manual\" automatic - Use automatic solver based on desired frequency
298\tdouble srcFreqMHz - Source clock frequency for the FLL in MHz.
299\tint Mult - Clock frequency multiplier.
300\tint Div - Clock frequency divider.
301\tint lockTolerance - FLL lock tolerance.
302\tboolean wcoIsSource - Is the WCO sourcing the FLL?
303\tdouble sourceAccuracy - FLL reference clock accuracy (%)?"
304}
305
306solve_fll
307