1 | /* |
2 | * Copyright 2018 Advanced Micro Devices, Inc. |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining a |
5 | * copy of this software and associated documentation files (the "Software"), |
6 | * to deal in the Software without restriction, including without limitation |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
8 | * and/or sell copies of the Software, and to permit persons to whom the |
9 | * Software is furnished to do so, subject to the following conditions: |
10 | * |
11 | * The above copyright notice and this permission notice shall be included in |
12 | * all copies or substantial portions of the Software. |
13 | * |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
20 | * OTHER DEALINGS IN THE SOFTWARE. |
21 | * |
22 | * Authors: AMD |
23 | * |
24 | */ |
25 | |
26 | #include "reg_helper.h" |
27 | #include "core_types.h" |
28 | #include "clk_mgr_internal.h" |
29 | #include "rv1_clk_mgr.h" |
30 | #include "dce100/dce_clk_mgr.h" |
31 | #include "dce112/dce112_clk_mgr.h" |
32 | #include "rv1_clk_mgr_vbios_smu.h" |
33 | #include "rv1_clk_mgr_clk.h" |
34 | |
35 | static void rv1_init_clocks(struct clk_mgr *clk_mgr) |
36 | { |
37 | memset(&(clk_mgr->clks), 0, sizeof(struct dc_clocks)); |
38 | } |
39 | |
40 | static int rv1_determine_dppclk_threshold(struct clk_mgr_internal *clk_mgr, struct dc_clocks *new_clocks) |
41 | { |
42 | bool request_dpp_div = new_clocks->dispclk_khz > new_clocks->dppclk_khz; |
43 | bool dispclk_increase = new_clocks->dispclk_khz > clk_mgr->base.clks.dispclk_khz; |
44 | int disp_clk_threshold = new_clocks->max_supported_dppclk_khz; |
45 | bool cur_dpp_div = clk_mgr->base.clks.dispclk_khz > clk_mgr->base.clks.dppclk_khz; |
46 | |
47 | /* increase clock, looking for div is 0 for current, request div is 1*/ |
48 | if (dispclk_increase) { |
49 | /* already divided by 2, no need to reach target clk with 2 steps*/ |
50 | if (cur_dpp_div) |
51 | return new_clocks->dispclk_khz; |
52 | |
53 | /* request disp clk is lower than maximum supported dpp clk, |
54 | * no need to reach target clk with two steps. |
55 | */ |
56 | if (new_clocks->dispclk_khz <= disp_clk_threshold) |
57 | return new_clocks->dispclk_khz; |
58 | |
59 | /* target dpp clk not request divided by 2, still within threshold */ |
60 | if (!request_dpp_div) |
61 | return new_clocks->dispclk_khz; |
62 | |
63 | } else { |
64 | /* decrease clock, looking for current dppclk divided by 2, |
65 | * request dppclk not divided by 2. |
66 | */ |
67 | |
68 | /* current dpp clk not divided by 2, no need to ramp*/ |
69 | if (!cur_dpp_div) |
70 | return new_clocks->dispclk_khz; |
71 | |
72 | /* current disp clk is lower than current maximum dpp clk, |
73 | * no need to ramp |
74 | */ |
75 | if (clk_mgr->base.clks.dispclk_khz <= disp_clk_threshold) |
76 | return new_clocks->dispclk_khz; |
77 | |
78 | /* request dpp clk need to be divided by 2 */ |
79 | if (request_dpp_div) |
80 | return new_clocks->dispclk_khz; |
81 | } |
82 | |
83 | return disp_clk_threshold; |
84 | } |
85 | |
86 | static void ramp_up_dispclk_with_dpp( |
87 | struct clk_mgr_internal *clk_mgr, |
88 | struct dc *dc, |
89 | struct dc_clocks *new_clocks, |
90 | bool safe_to_lower) |
91 | { |
92 | int i; |
93 | int dispclk_to_dpp_threshold = rv1_determine_dppclk_threshold(clk_mgr, new_clocks); |
94 | bool request_dpp_div = new_clocks->dispclk_khz > new_clocks->dppclk_khz; |
95 | |
96 | /* this function is to change dispclk, dppclk and dprefclk according to |
97 | * bandwidth requirement. Its call stack is rv1_update_clocks --> |
98 | * update_clocks --> dcn10_prepare_bandwidth / dcn10_optimize_bandwidth |
99 | * --> prepare_bandwidth / optimize_bandwidth. before change dcn hw, |
100 | * prepare_bandwidth will be called first to allow enough clock, |
101 | * watermark for change, after end of dcn hw change, optimize_bandwidth |
102 | * is executed to lower clock to save power for new dcn hw settings. |
103 | * |
104 | * below is sequence of commit_planes_for_stream: |
105 | * |
106 | * step 1: prepare_bandwidth - raise clock to have enough bandwidth |
107 | * step 2: lock_doublebuffer_enable |
108 | * step 3: pipe_control_lock(true) - make dchubp register change will |
109 | * not take effect right way |
110 | * step 4: apply_ctx_for_surface - program dchubp |
111 | * step 5: pipe_control_lock(false) - dchubp register change take effect |
112 | * step 6: optimize_bandwidth --> dc_post_update_surfaces_to_stream |
113 | * for full_date, optimize clock to save power |
114 | * |
115 | * at end of step 1, dcn clocks (dprefclk, dispclk, dppclk) may be |
116 | * changed for new dchubp configuration. but real dcn hub dchubps are |
117 | * still running with old configuration until end of step 5. this need |
118 | * clocks settings at step 1 should not less than that before step 1. |
119 | * this is checked by two conditions: 1. if (should_set_clock(safe_to_lower |
120 | * , new_clocks->dispclk_khz, clk_mgr_base->clks.dispclk_khz) || |
121 | * new_clocks->dispclk_khz == clk_mgr_base->clks.dispclk_khz) |
122 | * 2. request_dpp_div = new_clocks->dispclk_khz > new_clocks->dppclk_khz |
123 | * |
124 | * the second condition is based on new dchubp configuration. dppclk |
125 | * for new dchubp may be different from dppclk before step 1. |
126 | * for example, before step 1, dchubps are as below: |
127 | * pipe 0: recout=(0,40,1920,980) viewport=(0,0,1920,979) |
128 | * pipe 1: recout=(0,0,1920,1080) viewport=(0,0,1920,1080) |
129 | * for dppclk for pipe0 need dppclk = dispclk |
130 | * |
131 | * new dchubp pipe split configuration: |
132 | * pipe 0: recout=(0,0,960,1080) viewport=(0,0,960,1080) |
133 | * pipe 1: recout=(960,0,960,1080) viewport=(960,0,960,1080) |
134 | * dppclk only needs dppclk = dispclk /2. |
135 | * |
136 | * dispclk, dppclk are not lock by otg master lock. they take effect |
137 | * after step 1. during this transition, dispclk are the same, but |
138 | * dppclk is changed to half of previous clock for old dchubp |
139 | * configuration between step 1 and step 6. This may cause p-state |
140 | * warning intermittently. |
141 | * |
142 | * for new_clocks->dispclk_khz == clk_mgr_base->clks.dispclk_khz, we |
143 | * need make sure dppclk are not changed to less between step 1 and 6. |
144 | * for new_clocks->dispclk_khz > clk_mgr_base->clks.dispclk_khz, |
145 | * new display clock is raised, but we do not know ratio of |
146 | * new_clocks->dispclk_khz and clk_mgr_base->clks.dispclk_khz, |
147 | * new_clocks->dispclk_khz /2 does not guarantee equal or higher than |
148 | * old dppclk. we could ignore power saving different between |
149 | * dppclk = displck and dppclk = dispclk / 2 between step 1 and step 6. |
150 | * as long as safe_to_lower = false, set dpclk = dispclk to simplify |
151 | * condition check. |
152 | * todo: review this change for other asic. |
153 | **/ |
154 | if (!safe_to_lower) |
155 | request_dpp_div = false; |
156 | |
157 | /* set disp clk to dpp clk threshold */ |
158 | |
159 | clk_mgr->funcs->set_dispclk(clk_mgr, dispclk_to_dpp_threshold); |
160 | clk_mgr->funcs->set_dprefclk(clk_mgr); |
161 | |
162 | |
163 | /* update request dpp clk division option */ |
164 | for (i = 0; i < dc->res_pool->pipe_count; i++) { |
165 | struct pipe_ctx *pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i]; |
166 | |
167 | if (!pipe_ctx->plane_state) |
168 | continue; |
169 | |
170 | pipe_ctx->plane_res.dpp->funcs->dpp_dppclk_control( |
171 | pipe_ctx->plane_res.dpp, |
172 | request_dpp_div, |
173 | true); |
174 | } |
175 | |
176 | /* If target clk not same as dppclk threshold, set to target clock */ |
177 | if (dispclk_to_dpp_threshold != new_clocks->dispclk_khz) { |
178 | clk_mgr->funcs->set_dispclk(clk_mgr, new_clocks->dispclk_khz); |
179 | clk_mgr->funcs->set_dprefclk(clk_mgr); |
180 | } |
181 | |
182 | |
183 | clk_mgr->base.clks.dispclk_khz = new_clocks->dispclk_khz; |
184 | clk_mgr->base.clks.dppclk_khz = new_clocks->dppclk_khz; |
185 | clk_mgr->base.clks.max_supported_dppclk_khz = new_clocks->max_supported_dppclk_khz; |
186 | } |
187 | |
188 | static void rv1_update_clocks(struct clk_mgr *clk_mgr_base, |
189 | struct dc_state *context, |
190 | bool safe_to_lower) |
191 | { |
192 | struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base); |
193 | struct dc *dc = clk_mgr_base->ctx->dc; |
194 | struct dc_debug_options *debug = &dc->debug; |
195 | struct dc_clocks *new_clocks = &context->bw_ctx.bw.dcn.clk; |
196 | struct pp_smu_funcs_rv *pp_smu = NULL; |
197 | bool send_request_to_increase = false; |
198 | bool send_request_to_lower = false; |
199 | int display_count; |
200 | |
201 | bool enter_display_off = false; |
202 | |
203 | ASSERT(clk_mgr->pp_smu); |
204 | |
205 | if (dc->work_arounds.skip_clock_update) |
206 | return; |
207 | |
208 | pp_smu = &clk_mgr->pp_smu->rv_funcs; |
209 | |
210 | display_count = clk_mgr_helper_get_active_display_cnt(dc, context); |
211 | |
212 | if (display_count == 0) |
213 | enter_display_off = true; |
214 | |
215 | if (enter_display_off == safe_to_lower) { |
216 | /* |
217 | * Notify SMU active displays |
218 | * if function pointer not set up, this message is |
219 | * sent as part of pplib_apply_display_requirements. |
220 | */ |
221 | if (pp_smu->set_display_count) |
222 | pp_smu->set_display_count(&pp_smu->pp_smu, display_count); |
223 | } |
224 | |
225 | if (new_clocks->dispclk_khz > clk_mgr_base->clks.dispclk_khz |
226 | || new_clocks->phyclk_khz > clk_mgr_base->clks.phyclk_khz |
227 | || new_clocks->fclk_khz > clk_mgr_base->clks.fclk_khz |
228 | || new_clocks->dcfclk_khz > clk_mgr_base->clks.dcfclk_khz) |
229 | send_request_to_increase = true; |
230 | |
231 | if (should_set_clock(safe_to_lower, calc_clk: new_clocks->phyclk_khz, cur_clk: clk_mgr_base->clks.phyclk_khz)) { |
232 | clk_mgr_base->clks.phyclk_khz = new_clocks->phyclk_khz; |
233 | send_request_to_lower = true; |
234 | } |
235 | |
236 | // F Clock |
237 | if (debug->force_fclk_khz != 0) |
238 | new_clocks->fclk_khz = debug->force_fclk_khz; |
239 | |
240 | if (should_set_clock(safe_to_lower, calc_clk: new_clocks->fclk_khz, cur_clk: clk_mgr_base->clks.fclk_khz)) { |
241 | clk_mgr_base->clks.fclk_khz = new_clocks->fclk_khz; |
242 | send_request_to_lower = true; |
243 | } |
244 | |
245 | //DCF Clock |
246 | if (should_set_clock(safe_to_lower, calc_clk: new_clocks->dcfclk_khz, cur_clk: clk_mgr_base->clks.dcfclk_khz)) { |
247 | clk_mgr_base->clks.dcfclk_khz = new_clocks->dcfclk_khz; |
248 | send_request_to_lower = true; |
249 | } |
250 | |
251 | if (should_set_clock(safe_to_lower, |
252 | calc_clk: new_clocks->dcfclk_deep_sleep_khz, cur_clk: clk_mgr_base->clks.dcfclk_deep_sleep_khz)) { |
253 | clk_mgr_base->clks.dcfclk_deep_sleep_khz = new_clocks->dcfclk_deep_sleep_khz; |
254 | send_request_to_lower = true; |
255 | } |
256 | |
257 | /* make sure dcf clk is before dpp clk to |
258 | * make sure we have enough voltage to run dpp clk |
259 | */ |
260 | if (send_request_to_increase) { |
261 | /*use dcfclk to request voltage*/ |
262 | if (pp_smu->set_hard_min_fclk_by_freq && |
263 | pp_smu->set_hard_min_dcfclk_by_freq && |
264 | pp_smu->set_min_deep_sleep_dcfclk) { |
265 | pp_smu->set_hard_min_fclk_by_freq(&pp_smu->pp_smu, khz_to_mhz_ceil(khz: new_clocks->fclk_khz)); |
266 | pp_smu->set_hard_min_dcfclk_by_freq(&pp_smu->pp_smu, khz_to_mhz_ceil(khz: new_clocks->dcfclk_khz)); |
267 | pp_smu->set_min_deep_sleep_dcfclk(&pp_smu->pp_smu, khz_to_mhz_ceil(khz: new_clocks->dcfclk_deep_sleep_khz)); |
268 | } |
269 | } |
270 | |
271 | /* dcn1 dppclk is tied to dispclk */ |
272 | /* program dispclk on = as a w/a for sleep resume clock ramping issues */ |
273 | if (should_set_clock(safe_to_lower, calc_clk: new_clocks->dispclk_khz, cur_clk: clk_mgr_base->clks.dispclk_khz) |
274 | || new_clocks->dispclk_khz == clk_mgr_base->clks.dispclk_khz) { |
275 | ramp_up_dispclk_with_dpp(clk_mgr, dc, new_clocks, safe_to_lower); |
276 | clk_mgr_base->clks.dispclk_khz = new_clocks->dispclk_khz; |
277 | send_request_to_lower = true; |
278 | } |
279 | |
280 | if (!send_request_to_increase && send_request_to_lower) { |
281 | /*use dcfclk to request voltage*/ |
282 | if (pp_smu->set_hard_min_fclk_by_freq && |
283 | pp_smu->set_hard_min_dcfclk_by_freq && |
284 | pp_smu->set_min_deep_sleep_dcfclk) { |
285 | pp_smu->set_hard_min_fclk_by_freq(&pp_smu->pp_smu, khz_to_mhz_ceil(khz: new_clocks->fclk_khz)); |
286 | pp_smu->set_hard_min_dcfclk_by_freq(&pp_smu->pp_smu, khz_to_mhz_ceil(khz: new_clocks->dcfclk_khz)); |
287 | pp_smu->set_min_deep_sleep_dcfclk(&pp_smu->pp_smu, khz_to_mhz_ceil(khz: new_clocks->dcfclk_deep_sleep_khz)); |
288 | } |
289 | } |
290 | } |
291 | |
292 | static void rv1_enable_pme_wa(struct clk_mgr *clk_mgr_base) |
293 | { |
294 | struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base); |
295 | struct pp_smu_funcs_rv *pp_smu = NULL; |
296 | |
297 | if (clk_mgr->pp_smu) { |
298 | pp_smu = &clk_mgr->pp_smu->rv_funcs; |
299 | |
300 | if (pp_smu->set_pme_wa_enable) |
301 | pp_smu->set_pme_wa_enable(&pp_smu->pp_smu); |
302 | } |
303 | } |
304 | |
305 | static struct clk_mgr_funcs rv1_clk_funcs = { |
306 | .init_clocks = rv1_init_clocks, |
307 | .get_dp_ref_clk_frequency = dce12_get_dp_ref_freq_khz, |
308 | .update_clocks = rv1_update_clocks, |
309 | .enable_pme_wa = rv1_enable_pme_wa, |
310 | }; |
311 | |
312 | static struct clk_mgr_internal_funcs rv1_clk_internal_funcs = { |
313 | .set_dispclk = rv1_vbios_smu_set_dispclk, |
314 | .set_dprefclk = dce112_set_dprefclk |
315 | }; |
316 | |
317 | void rv1_clk_mgr_construct(struct dc_context *ctx, struct clk_mgr_internal *clk_mgr, struct pp_smu_funcs *pp_smu) |
318 | { |
319 | struct dc_debug_options *debug = &ctx->dc->debug; |
320 | struct dc_bios *bp = ctx->dc_bios; |
321 | |
322 | clk_mgr->base.ctx = ctx; |
323 | clk_mgr->pp_smu = pp_smu; |
324 | clk_mgr->base.funcs = &rv1_clk_funcs; |
325 | clk_mgr->funcs = &rv1_clk_internal_funcs; |
326 | |
327 | clk_mgr->dfs_bypass_disp_clk = 0; |
328 | |
329 | clk_mgr->dprefclk_ss_percentage = 0; |
330 | clk_mgr->dprefclk_ss_divider = 1000; |
331 | clk_mgr->ss_on_dprefclk = false; |
332 | clk_mgr->base.dprefclk_khz = 600000; |
333 | |
334 | if (bp->integrated_info) |
335 | clk_mgr->base.dentist_vco_freq_khz = bp->integrated_info->dentist_vco_freq; |
336 | if (bp->fw_info_valid && clk_mgr->base.dentist_vco_freq_khz == 0) { |
337 | clk_mgr->base.dentist_vco_freq_khz = bp->fw_info.smu_gpu_pll_output_freq; |
338 | if (clk_mgr->base.dentist_vco_freq_khz == 0) |
339 | clk_mgr->base.dentist_vco_freq_khz = 3600000; |
340 | } |
341 | |
342 | if (!debug->disable_dfs_bypass && bp->integrated_info) |
343 | if (bp->integrated_info->gpu_cap_info & DFS_BYPASS_ENABLE) |
344 | clk_mgr->dfs_bypass_enabled = true; |
345 | |
346 | dce_clock_read_ss_info(dccg_dce: clk_mgr); |
347 | } |
348 | |
349 | |
350 | |