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_IDX_SRC_FREQ 0
23const ARG_IDX_TARGET_FREQ 1
24const ARG_IDX_LF_MODE 2
25const ARG_IDX_MIN_POWER 3
26
27const FEEDBACK_DIV_KEY "feedbackDiv"
28const REFERENCE_DIV_KEY "referenceDiv"
29const OUTPUT_DIV_KEY "outputDiv"
30const FRAC_DIV_KEY "fracDiv"
31
32const MIN_IN_FREQ 4
33const MAX_IN_FREQ 64
34const FVCO_MIN 400
35const FVCO_MAX 800
36
37# PLL OUTPUT_DIV bitfield allowable range
38const OUT_MIN 2
39const OUT_MAX 16
40
41# PLL REFERENCE_DIV bitfield allowable range
42const REF_MIN 1
43const REF_MAX 16
44
45# PLL FEEDBACK_DIV bitfield allowable ranges
46const FB_MIN 16
47const FB_MAX 200
48
49const SUCCESS 0
50const ERROR_ARG_COUNT 1
51const ERROR_ID 2
52const ERROR_ARG_VALUE 3
53
54set channelName stdout
55
56if {[chan names ModusToolbox] eq "ModusToolbox"} {
57    set channelName ModusToolbox
58}
59
60proc solve_pll {} {
61    if {$::argc != $::ARG_IDX_MIN_POWER + 1} {
62        error "PLL Solver requires 4 arguments:
63\tdouble srcFreqMHz - Source clock frequency for the PLL in MHz.
64\tdouble targetFreqMHz - Output frequency of the PLL in MHz.
65\tboolean lfMode - CLK_PLL_CONFIG register, PLL_LF_MODE bit.
66\tboolean minPower - Optimize for min power rather min jitter."
67        return $::ERROR_ARG_COUNT
68    }
69    set srcFreqMHz [lindex $::argv $::ARG_IDX_SRC_FREQ]
70    set targetFreqMHz [lindex $::argv $::ARG_IDX_TARGET_FREQ]
71    set lfMode [string is true [lindex $::argv $::ARG_IDX_LF_MODE]]
72    set minPower [string is true [lindex $::argv $::ARG_IDX_MIN_POWER]]
73    if {![string is double $srcFreqMHz] || ![string is double $targetFreqMHz]} {
74        error "Unable to parse argument values: either $srcFreqMHz or $targetFreqMHz is not a floating-point number."
75        return $::ERROR_ARG_VALUE
76    }
77    set srcFreqMHz [expr {double($srcFreqMHz)}]
78    set targetFreqMHz [expr {double($targetFreqMHz)}]
79    if {$srcFreqMHz < $::MIN_IN_FREQ || $::MAX_IN_FREQ < $srcFreqMHz} {
80        error "Invalid PLL input frequency '$srcFreqMHz'. Must be within the range $::MIN_IN_FREQ-$::MAX_IN_FREQ."
81        return $::ERROR_ARG_VALUE
82    }
83    return [solve_pll_internal $srcFreqMHz $targetFreqMHz $lfMode $minPower]
84}
85
86proc solve_pll_internal {srcFreqMHz targetFreqMHz lfMode minPower} {
87    set divFb $::FB_MIN
88    set divRef $::REF_MIN
89    set divOut $::OUT_MIN
90    set fracDiv 0
91    set foutBest 0
92
93    for {set q $::REF_MIN} {$q <= $::REF_MAX} {incr q} {
94        for {set p $::FB_MIN} {$p <= $::FB_MAX} {incr p} {
95            set fvco [expr {($srcFreqMHz * $p)/ $q}]
96
97            if {![is_pfdref_in_range $fvco]} {
98                continue
99            }
100            for {set o $::OUT_MIN} {$o <= $::OUT_MAX} {incr o} {
101                set tempVco [expr { $targetFreqMHz * $o}]
102                set tempFeedBackDivLeftShifted [expr { ((($tempVco) * pow (2, 24)) * $q) / $srcFreqMHz }]
103                set feedBackFracDiv [expr { (int($tempFeedBackDivLeftShifted) & (( 1 << 24 ) - 1)) }]
104                set fout [expr {($srcFreqMHz * ( ($p << 24) + $feedBackFracDiv) / ($q * $o)) / pow (2, 24)}]
105                if {int($foutBest) == int($targetFreqMHz)}   {
106                       break;
107                }
108                 set foutBest $fout
109                 set divFb $p
110                 set divRef $q
111                 set divOut $o
112                 set fracDiv $feedBackFracDiv
113            }
114        }
115    }
116    puts $::channelName "param:$::FEEDBACK_DIV_KEY=$divFb"
117    puts $::channelName "param:$::REFERENCE_DIV_KEY=$divRef"
118    puts $::channelName "param:$::OUTPUT_DIV_KEY=$divOut"
119    puts $::channelName "param:$::FRAC_DIV_KEY=$fracDiv"
120    return $::SUCCESS
121}
122
123proc is_pfdref_in_range {fvco} {
124    return [expr {$::FVCO_MIN <= $fvco && $fvco <= $::FVCO_MAX}]
125}
126
127solve_pll
128