1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * OMAP4-specific DPLL control functions |
4 | * |
5 | * Copyright (C) 2011 Texas Instruments, Inc. |
6 | * Rajendra Nayak |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/errno.h> |
11 | #include <linux/clk.h> |
12 | #include <linux/io.h> |
13 | #include <linux/bitops.h> |
14 | #include <linux/clk/ti.h> |
15 | |
16 | #include "clock.h" |
17 | |
18 | /* |
19 | * Maximum DPLL input frequency (FINT) and output frequency (FOUT) that |
20 | * can supported when using the DPLL low-power mode. Frequencies are |
21 | * defined in OMAP4430/60 Public TRM section 3.6.3.3.2 "Enable Control, |
22 | * Status, and Low-Power Operation Mode". |
23 | */ |
24 | #define OMAP4_DPLL_LP_FINT_MAX 1000000 |
25 | #define OMAP4_DPLL_LP_FOUT_MAX 100000000 |
26 | |
27 | /* |
28 | * Bitfield declarations |
29 | */ |
30 | #define OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK BIT(8) |
31 | #define OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK BIT(10) |
32 | #define OMAP4430_DPLL_REGM4XEN_MASK BIT(11) |
33 | |
34 | /* Static rate multiplier for OMAP4 REGM4XEN clocks */ |
35 | #define OMAP4430_REGM4XEN_MULT 4 |
36 | |
37 | static void omap4_dpllmx_allow_gatectrl(struct clk_hw_omap *clk) |
38 | { |
39 | u32 v; |
40 | u32 mask; |
41 | |
42 | if (!clk) |
43 | return; |
44 | |
45 | mask = clk->flags & CLOCK_CLKOUTX2 ? |
46 | OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK : |
47 | OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK; |
48 | |
49 | v = ti_clk_ll_ops->clk_readl(&clk->clksel_reg); |
50 | /* Clear the bit to allow gatectrl */ |
51 | v &= ~mask; |
52 | ti_clk_ll_ops->clk_writel(v, &clk->clksel_reg); |
53 | } |
54 | |
55 | static void omap4_dpllmx_deny_gatectrl(struct clk_hw_omap *clk) |
56 | { |
57 | u32 v; |
58 | u32 mask; |
59 | |
60 | if (!clk) |
61 | return; |
62 | |
63 | mask = clk->flags & CLOCK_CLKOUTX2 ? |
64 | OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK : |
65 | OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK; |
66 | |
67 | v = ti_clk_ll_ops->clk_readl(&clk->clksel_reg); |
68 | /* Set the bit to deny gatectrl */ |
69 | v |= mask; |
70 | ti_clk_ll_ops->clk_writel(v, &clk->clksel_reg); |
71 | } |
72 | |
73 | const struct clk_hw_omap_ops clkhwops_omap4_dpllmx = { |
74 | .allow_idle = omap4_dpllmx_allow_gatectrl, |
75 | .deny_idle = omap4_dpllmx_deny_gatectrl, |
76 | }; |
77 | |
78 | /** |
79 | * omap4_dpll_lpmode_recalc - compute DPLL low-power setting |
80 | * @dd: pointer to the dpll data structure |
81 | * |
82 | * Calculates if low-power mode can be enabled based upon the last |
83 | * multiplier and divider values calculated. If low-power mode can be |
84 | * enabled, then the bit to enable low-power mode is stored in the |
85 | * last_rounded_lpmode variable. This implementation is based upon the |
86 | * criteria for enabling low-power mode as described in the OMAP4430/60 |
87 | * Public TRM section 3.6.3.3.2 "Enable Control, Status, and Low-Power |
88 | * Operation Mode". |
89 | */ |
90 | static void omap4_dpll_lpmode_recalc(struct dpll_data *dd) |
91 | { |
92 | long fint, fout; |
93 | |
94 | fint = clk_hw_get_rate(hw: dd->clk_ref) / (dd->last_rounded_n + 1); |
95 | fout = fint * dd->last_rounded_m; |
96 | |
97 | if ((fint < OMAP4_DPLL_LP_FINT_MAX) && (fout < OMAP4_DPLL_LP_FOUT_MAX)) |
98 | dd->last_rounded_lpmode = 1; |
99 | else |
100 | dd->last_rounded_lpmode = 0; |
101 | } |
102 | |
103 | /** |
104 | * omap4_dpll_regm4xen_recalc - compute DPLL rate, considering REGM4XEN bit |
105 | * @hw: pointer to the clock to compute the rate for |
106 | * @parent_rate: clock rate of the DPLL parent |
107 | * |
108 | * Compute the output rate for the OMAP4 DPLL represented by @clk. |
109 | * Takes the REGM4XEN bit into consideration, which is needed for the |
110 | * OMAP4 ABE DPLL. Returns the DPLL's output rate (before M-dividers) |
111 | * upon success, or 0 upon error. |
112 | */ |
113 | unsigned long omap4_dpll_regm4xen_recalc(struct clk_hw *hw, |
114 | unsigned long parent_rate) |
115 | { |
116 | struct clk_hw_omap *clk = to_clk_hw_omap(hw); |
117 | u32 v; |
118 | unsigned long rate; |
119 | struct dpll_data *dd; |
120 | |
121 | if (!clk || !clk->dpll_data) |
122 | return 0; |
123 | |
124 | dd = clk->dpll_data; |
125 | |
126 | rate = omap2_get_dpll_rate(clk); |
127 | |
128 | /* regm4xen adds a multiplier of 4 to DPLL calculations */ |
129 | v = ti_clk_ll_ops->clk_readl(&dd->control_reg); |
130 | if (v & OMAP4430_DPLL_REGM4XEN_MASK) |
131 | rate *= OMAP4430_REGM4XEN_MULT; |
132 | |
133 | return rate; |
134 | } |
135 | |
136 | /** |
137 | * omap4_dpll_regm4xen_round_rate - round DPLL rate, considering REGM4XEN bit |
138 | * @hw: struct hw_clk containing the struct clk * of the DPLL to round a rate for |
139 | * @target_rate: the desired rate of the DPLL |
140 | * @parent_rate: clock rate of the DPLL parent |
141 | * |
142 | * Compute the rate that would be programmed into the DPLL hardware |
143 | * for @clk if set_rate() were to be provided with the rate |
144 | * @target_rate. Takes the REGM4XEN bit into consideration, which is |
145 | * needed for the OMAP4 ABE DPLL. Returns the rounded rate (before |
146 | * M-dividers) upon success, -EINVAL if @clk is null or not a DPLL, or |
147 | * ~0 if an error occurred in omap2_dpll_round_rate(). |
148 | */ |
149 | long omap4_dpll_regm4xen_round_rate(struct clk_hw *hw, |
150 | unsigned long target_rate, |
151 | unsigned long *parent_rate) |
152 | { |
153 | struct clk_hw_omap *clk = to_clk_hw_omap(hw); |
154 | struct dpll_data *dd; |
155 | long r; |
156 | |
157 | if (!clk || !clk->dpll_data) |
158 | return -EINVAL; |
159 | |
160 | dd = clk->dpll_data; |
161 | |
162 | dd->last_rounded_m4xen = 0; |
163 | |
164 | /* |
165 | * First try to compute the DPLL configuration for |
166 | * target rate without using the 4X multiplier. |
167 | */ |
168 | r = omap2_dpll_round_rate(hw, target_rate, NULL); |
169 | if (r != ~0) |
170 | goto out; |
171 | |
172 | /* |
173 | * If we did not find a valid DPLL configuration, try again, but |
174 | * this time see if using the 4X multiplier can help. Enabling the |
175 | * 4X multiplier is equivalent to dividing the target rate by 4. |
176 | */ |
177 | r = omap2_dpll_round_rate(hw, target_rate: target_rate / OMAP4430_REGM4XEN_MULT, |
178 | NULL); |
179 | if (r == ~0) |
180 | return r; |
181 | |
182 | dd->last_rounded_rate *= OMAP4430_REGM4XEN_MULT; |
183 | dd->last_rounded_m4xen = 1; |
184 | |
185 | out: |
186 | omap4_dpll_lpmode_recalc(dd); |
187 | |
188 | return dd->last_rounded_rate; |
189 | } |
190 | |
191 | /** |
192 | * omap4_dpll_regm4xen_determine_rate - determine rate for a DPLL |
193 | * @hw: pointer to the clock to determine rate for |
194 | * @req: target rate request |
195 | * |
196 | * Determines which DPLL mode to use for reaching a desired rate. |
197 | * Checks whether the DPLL shall be in bypass or locked mode, and if |
198 | * locked, calculates the M,N values for the DPLL via round-rate. |
199 | * Returns 0 on success and a negative error value otherwise. |
200 | */ |
201 | int omap4_dpll_regm4xen_determine_rate(struct clk_hw *hw, |
202 | struct clk_rate_request *req) |
203 | { |
204 | struct clk_hw_omap *clk = to_clk_hw_omap(hw); |
205 | struct dpll_data *dd; |
206 | |
207 | if (!req->rate) |
208 | return -EINVAL; |
209 | |
210 | dd = clk->dpll_data; |
211 | if (!dd) |
212 | return -EINVAL; |
213 | |
214 | if (clk_hw_get_rate(hw: dd->clk_bypass) == req->rate && |
215 | (dd->modes & (1 << DPLL_LOW_POWER_BYPASS))) { |
216 | req->best_parent_hw = dd->clk_bypass; |
217 | } else { |
218 | req->rate = omap4_dpll_regm4xen_round_rate(hw, target_rate: req->rate, |
219 | parent_rate: &req->best_parent_rate); |
220 | req->best_parent_hw = dd->clk_ref; |
221 | } |
222 | |
223 | req->best_parent_rate = req->rate; |
224 | |
225 | return 0; |
226 | } |
227 | |