Line data Source code
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 : #include <linux/delay.h>
26 : #include "dm_services.h"
27 : #include "dcn20/dcn20_hubbub.h"
28 : #include "dcn21_hubbub.h"
29 : #include "reg_helper.h"
30 :
31 : #define REG(reg)\
32 : hubbub1->regs->reg
33 : #define DC_LOGGER \
34 : hubbub1->base.ctx->logger
35 : #define CTX \
36 : hubbub1->base.ctx
37 :
38 : #undef FN
39 : #define FN(reg_name, field_name) \
40 : hubbub1->shifts->field_name, hubbub1->masks->field_name
41 :
42 : #define REG(reg)\
43 : hubbub1->regs->reg
44 :
45 : #define CTX \
46 : hubbub1->base.ctx
47 :
48 : #undef FN
49 : #define FN(reg_name, field_name) \
50 : hubbub1->shifts->field_name, hubbub1->masks->field_name
51 :
52 : static uint32_t convert_and_clamp(
53 : uint32_t wm_ns,
54 : uint32_t refclk_mhz,
55 : uint32_t clamp_value)
56 : {
57 0 : uint32_t ret_val = 0;
58 0 : ret_val = wm_ns * refclk_mhz;
59 0 : ret_val /= 1000;
60 :
61 0 : if (ret_val > clamp_value)
62 0 : ret_val = clamp_value;
63 :
64 : return ret_val;
65 : }
66 :
67 0 : void dcn21_dchvm_init(struct hubbub *hubbub)
68 : {
69 0 : struct dcn20_hubbub *hubbub1 = TO_DCN20_HUBBUB(hubbub);
70 : uint32_t riommu_active, prefetch_done;
71 : int i;
72 :
73 0 : REG_GET(DCHVM_RIOMMU_STAT0, HOSTVM_PREFETCH_DONE, &prefetch_done);
74 :
75 0 : if (prefetch_done) {
76 0 : hubbub->riommu_active = true;
77 0 : return;
78 : }
79 : //Init DCHVM block
80 0 : REG_UPDATE(DCHVM_CTRL0, HOSTVM_INIT_REQ, 1);
81 :
82 : //Poll until RIOMMU_ACTIVE = 1
83 0 : for (i = 0; i < 100; i++) {
84 0 : REG_GET(DCHVM_RIOMMU_STAT0, RIOMMU_ACTIVE, &riommu_active);
85 :
86 0 : if (riommu_active)
87 : break;
88 : else
89 0 : udelay(5);
90 : }
91 :
92 0 : if (riommu_active) {
93 : //Reflect the power status of DCHUBBUB
94 0 : REG_UPDATE(DCHVM_RIOMMU_CTRL0, HOSTVM_POWERSTATUS, 1);
95 :
96 : //Start rIOMMU prefetching
97 0 : REG_UPDATE(DCHVM_RIOMMU_CTRL0, HOSTVM_PREFETCH_REQ, 1);
98 :
99 : // Enable dynamic clock gating
100 0 : REG_UPDATE_4(DCHVM_CLK_CTRL,
101 : HVM_DISPCLK_R_GATE_DIS, 0,
102 : HVM_DISPCLK_G_GATE_DIS, 0,
103 : HVM_DCFCLK_R_GATE_DIS, 0,
104 : HVM_DCFCLK_G_GATE_DIS, 0);
105 :
106 : //Poll until HOSTVM_PREFETCH_DONE = 1
107 0 : REG_WAIT(DCHVM_RIOMMU_STAT0, HOSTVM_PREFETCH_DONE, 1, 5, 100);
108 :
109 0 : hubbub->riommu_active = true;
110 : }
111 : }
112 :
113 0 : int hubbub21_init_dchub(struct hubbub *hubbub,
114 : struct dcn_hubbub_phys_addr_config *pa_config)
115 : {
116 0 : struct dcn20_hubbub *hubbub1 = TO_DCN20_HUBBUB(hubbub);
117 : struct dcn_vmid_page_table_config phys_config;
118 :
119 0 : REG_SET(DCN_VM_FB_LOCATION_BASE, 0,
120 : FB_BASE, pa_config->system_aperture.fb_base >> 24);
121 0 : REG_SET(DCN_VM_FB_LOCATION_TOP, 0,
122 : FB_TOP, pa_config->system_aperture.fb_top >> 24);
123 0 : REG_SET(DCN_VM_FB_OFFSET, 0,
124 : FB_OFFSET, pa_config->system_aperture.fb_offset >> 24);
125 0 : REG_SET(DCN_VM_AGP_BOT, 0,
126 : AGP_BOT, pa_config->system_aperture.agp_bot >> 24);
127 0 : REG_SET(DCN_VM_AGP_TOP, 0,
128 : AGP_TOP, pa_config->system_aperture.agp_top >> 24);
129 0 : REG_SET(DCN_VM_AGP_BASE, 0,
130 : AGP_BASE, pa_config->system_aperture.agp_base >> 24);
131 :
132 0 : if (pa_config->gart_config.page_table_start_addr != pa_config->gart_config.page_table_end_addr) {
133 0 : phys_config.page_table_start_addr = pa_config->gart_config.page_table_start_addr >> 12;
134 0 : phys_config.page_table_end_addr = pa_config->gart_config.page_table_end_addr >> 12;
135 0 : phys_config.page_table_base_addr = pa_config->gart_config.page_table_base_addr | 1; //Note: hack
136 0 : phys_config.depth = 0;
137 0 : phys_config.block_size = 0;
138 : // Init VMID 0 based on PA config
139 0 : dcn20_vmid_setup(&hubbub1->vmid[0], &phys_config);
140 : }
141 :
142 0 : dcn21_dchvm_init(hubbub);
143 :
144 0 : return hubbub1->num_vmid;
145 : }
146 :
147 0 : bool hubbub21_program_urgent_watermarks(
148 : struct hubbub *hubbub,
149 : struct dcn_watermark_set *watermarks,
150 : unsigned int refclk_mhz,
151 : bool safe_to_lower)
152 : {
153 0 : struct dcn20_hubbub *hubbub1 = TO_DCN20_HUBBUB(hubbub);
154 : uint32_t prog_wm_value;
155 0 : bool wm_pending = false;
156 :
157 : /* Repeat for water mark set A, B, C and D. */
158 : /* clock state A */
159 0 : if (safe_to_lower || watermarks->a.urgent_ns > hubbub1->watermarks.a.urgent_ns) {
160 0 : hubbub1->watermarks.a.urgent_ns = watermarks->a.urgent_ns;
161 0 : prog_wm_value = convert_and_clamp(watermarks->a.urgent_ns,
162 : refclk_mhz, 0x1fffff);
163 0 : REG_SET_2(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_A, 0,
164 : DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_A, prog_wm_value,
165 : DCHUBBUB_ARB_VM_ROW_URGENCY_WATERMARK_A, prog_wm_value);
166 :
167 0 : DC_LOG_BANDWIDTH_CALCS("URGENCY_WATERMARK_A calculated =%d\n"
168 : "HW register value = 0x%x\n",
169 : watermarks->a.urgent_ns, prog_wm_value);
170 0 : } else if (watermarks->a.urgent_ns < hubbub1->watermarks.a.urgent_ns)
171 0 : wm_pending = true;
172 :
173 : /* determine the transfer time for a quantity of data for a particular requestor.*/
174 0 : if (safe_to_lower || watermarks->a.frac_urg_bw_flip
175 0 : > hubbub1->watermarks.a.frac_urg_bw_flip) {
176 0 : hubbub1->watermarks.a.frac_urg_bw_flip = watermarks->a.frac_urg_bw_flip;
177 :
178 0 : REG_SET(DCHUBBUB_ARB_FRAC_URG_BW_FLIP_A, 0,
179 : DCHUBBUB_ARB_FRAC_URG_BW_FLIP_A, watermarks->a.frac_urg_bw_flip);
180 0 : } else if (watermarks->a.frac_urg_bw_flip
181 : < hubbub1->watermarks.a.frac_urg_bw_flip)
182 0 : wm_pending = true;
183 :
184 0 : if (safe_to_lower || watermarks->a.frac_urg_bw_nom
185 0 : > hubbub1->watermarks.a.frac_urg_bw_nom) {
186 0 : hubbub1->watermarks.a.frac_urg_bw_nom = watermarks->a.frac_urg_bw_nom;
187 :
188 0 : REG_SET(DCHUBBUB_ARB_FRAC_URG_BW_NOM_A, 0,
189 : DCHUBBUB_ARB_FRAC_URG_BW_NOM_A, watermarks->a.frac_urg_bw_nom);
190 0 : } else if (watermarks->a.frac_urg_bw_nom
191 : < hubbub1->watermarks.a.frac_urg_bw_nom)
192 0 : wm_pending = true;
193 :
194 0 : if (safe_to_lower || watermarks->a.urgent_latency_ns > hubbub1->watermarks.a.urgent_latency_ns) {
195 0 : hubbub1->watermarks.a.urgent_latency_ns = watermarks->a.urgent_latency_ns;
196 0 : prog_wm_value = convert_and_clamp(watermarks->a.urgent_latency_ns,
197 : refclk_mhz, 0x1fffff);
198 0 : REG_SET(DCHUBBUB_ARB_REFCYC_PER_TRIP_TO_MEMORY_A, 0,
199 : DCHUBBUB_ARB_REFCYC_PER_TRIP_TO_MEMORY_A, prog_wm_value);
200 0 : } else if (watermarks->a.urgent_latency_ns < hubbub1->watermarks.a.urgent_latency_ns)
201 0 : wm_pending = true;
202 :
203 : /* clock state B */
204 0 : if (safe_to_lower || watermarks->b.urgent_ns > hubbub1->watermarks.b.urgent_ns) {
205 0 : hubbub1->watermarks.b.urgent_ns = watermarks->b.urgent_ns;
206 0 : prog_wm_value = convert_and_clamp(watermarks->b.urgent_ns,
207 : refclk_mhz, 0x1fffff);
208 0 : REG_SET_2(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_B, 0,
209 : DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_B, prog_wm_value,
210 : DCHUBBUB_ARB_VM_ROW_URGENCY_WATERMARK_B, prog_wm_value);
211 :
212 0 : DC_LOG_BANDWIDTH_CALCS("URGENCY_WATERMARK_B calculated =%d\n"
213 : "HW register value = 0x%x\n",
214 : watermarks->b.urgent_ns, prog_wm_value);
215 0 : } else if (watermarks->b.urgent_ns < hubbub1->watermarks.b.urgent_ns)
216 0 : wm_pending = true;
217 :
218 : /* determine the transfer time for a quantity of data for a particular requestor.*/
219 0 : if (safe_to_lower || watermarks->a.frac_urg_bw_flip
220 0 : > hubbub1->watermarks.a.frac_urg_bw_flip) {
221 0 : hubbub1->watermarks.a.frac_urg_bw_flip = watermarks->a.frac_urg_bw_flip;
222 :
223 0 : REG_SET(DCHUBBUB_ARB_FRAC_URG_BW_FLIP_B, 0,
224 : DCHUBBUB_ARB_FRAC_URG_BW_FLIP_B, watermarks->a.frac_urg_bw_flip);
225 0 : } else if (watermarks->a.frac_urg_bw_flip
226 : < hubbub1->watermarks.a.frac_urg_bw_flip)
227 0 : wm_pending = true;
228 :
229 0 : if (safe_to_lower || watermarks->a.frac_urg_bw_nom
230 0 : > hubbub1->watermarks.a.frac_urg_bw_nom) {
231 0 : hubbub1->watermarks.a.frac_urg_bw_nom = watermarks->a.frac_urg_bw_nom;
232 :
233 0 : REG_SET(DCHUBBUB_ARB_FRAC_URG_BW_NOM_B, 0,
234 : DCHUBBUB_ARB_FRAC_URG_BW_NOM_B, watermarks->a.frac_urg_bw_nom);
235 0 : } else if (watermarks->a.frac_urg_bw_nom
236 : < hubbub1->watermarks.a.frac_urg_bw_nom)
237 0 : wm_pending = true;
238 :
239 0 : if (safe_to_lower || watermarks->b.urgent_latency_ns > hubbub1->watermarks.b.urgent_latency_ns) {
240 0 : hubbub1->watermarks.b.urgent_latency_ns = watermarks->b.urgent_latency_ns;
241 0 : prog_wm_value = convert_and_clamp(watermarks->b.urgent_latency_ns,
242 : refclk_mhz, 0x1fffff);
243 0 : REG_SET(DCHUBBUB_ARB_REFCYC_PER_TRIP_TO_MEMORY_B, 0,
244 : DCHUBBUB_ARB_REFCYC_PER_TRIP_TO_MEMORY_B, prog_wm_value);
245 0 : } else if (watermarks->b.urgent_latency_ns < hubbub1->watermarks.b.urgent_latency_ns)
246 0 : wm_pending = true;
247 :
248 : /* clock state C */
249 0 : if (safe_to_lower || watermarks->c.urgent_ns > hubbub1->watermarks.c.urgent_ns) {
250 0 : hubbub1->watermarks.c.urgent_ns = watermarks->c.urgent_ns;
251 0 : prog_wm_value = convert_and_clamp(watermarks->c.urgent_ns,
252 : refclk_mhz, 0x1fffff);
253 0 : REG_SET_2(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_C, 0,
254 : DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_C, prog_wm_value,
255 : DCHUBBUB_ARB_VM_ROW_URGENCY_WATERMARK_C, prog_wm_value);
256 :
257 0 : DC_LOG_BANDWIDTH_CALCS("URGENCY_WATERMARK_C calculated =%d\n"
258 : "HW register value = 0x%x\n",
259 : watermarks->c.urgent_ns, prog_wm_value);
260 0 : } else if (watermarks->c.urgent_ns < hubbub1->watermarks.c.urgent_ns)
261 0 : wm_pending = true;
262 :
263 : /* determine the transfer time for a quantity of data for a particular requestor.*/
264 0 : if (safe_to_lower || watermarks->a.frac_urg_bw_flip
265 0 : > hubbub1->watermarks.a.frac_urg_bw_flip) {
266 0 : hubbub1->watermarks.a.frac_urg_bw_flip = watermarks->a.frac_urg_bw_flip;
267 :
268 0 : REG_SET(DCHUBBUB_ARB_FRAC_URG_BW_FLIP_C, 0,
269 : DCHUBBUB_ARB_FRAC_URG_BW_FLIP_C, watermarks->a.frac_urg_bw_flip);
270 0 : } else if (watermarks->a.frac_urg_bw_flip
271 : < hubbub1->watermarks.a.frac_urg_bw_flip)
272 0 : wm_pending = true;
273 :
274 0 : if (safe_to_lower || watermarks->a.frac_urg_bw_nom
275 0 : > hubbub1->watermarks.a.frac_urg_bw_nom) {
276 0 : hubbub1->watermarks.a.frac_urg_bw_nom = watermarks->a.frac_urg_bw_nom;
277 :
278 0 : REG_SET(DCHUBBUB_ARB_FRAC_URG_BW_NOM_C, 0,
279 : DCHUBBUB_ARB_FRAC_URG_BW_NOM_C, watermarks->a.frac_urg_bw_nom);
280 0 : } else if (watermarks->a.frac_urg_bw_nom
281 : < hubbub1->watermarks.a.frac_urg_bw_nom)
282 0 : wm_pending = true;
283 :
284 0 : if (safe_to_lower || watermarks->c.urgent_latency_ns > hubbub1->watermarks.c.urgent_latency_ns) {
285 0 : hubbub1->watermarks.c.urgent_latency_ns = watermarks->c.urgent_latency_ns;
286 0 : prog_wm_value = convert_and_clamp(watermarks->c.urgent_latency_ns,
287 : refclk_mhz, 0x1fffff);
288 0 : REG_SET(DCHUBBUB_ARB_REFCYC_PER_TRIP_TO_MEMORY_C, 0,
289 : DCHUBBUB_ARB_REFCYC_PER_TRIP_TO_MEMORY_C, prog_wm_value);
290 0 : } else if (watermarks->c.urgent_latency_ns < hubbub1->watermarks.c.urgent_latency_ns)
291 0 : wm_pending = true;
292 :
293 : /* clock state D */
294 0 : if (safe_to_lower || watermarks->d.urgent_ns > hubbub1->watermarks.d.urgent_ns) {
295 0 : hubbub1->watermarks.d.urgent_ns = watermarks->d.urgent_ns;
296 0 : prog_wm_value = convert_and_clamp(watermarks->d.urgent_ns,
297 : refclk_mhz, 0x1fffff);
298 0 : REG_SET_2(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_D, 0,
299 : DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_D, prog_wm_value,
300 : DCHUBBUB_ARB_VM_ROW_URGENCY_WATERMARK_D, prog_wm_value);
301 :
302 0 : DC_LOG_BANDWIDTH_CALCS("URGENCY_WATERMARK_D calculated =%d\n"
303 : "HW register value = 0x%x\n",
304 : watermarks->d.urgent_ns, prog_wm_value);
305 0 : } else if (watermarks->d.urgent_ns < hubbub1->watermarks.d.urgent_ns)
306 0 : wm_pending = true;
307 :
308 : /* determine the transfer time for a quantity of data for a particular requestor.*/
309 0 : if (safe_to_lower || watermarks->a.frac_urg_bw_flip
310 0 : > hubbub1->watermarks.a.frac_urg_bw_flip) {
311 0 : hubbub1->watermarks.a.frac_urg_bw_flip = watermarks->a.frac_urg_bw_flip;
312 :
313 0 : REG_SET(DCHUBBUB_ARB_FRAC_URG_BW_FLIP_D, 0,
314 : DCHUBBUB_ARB_FRAC_URG_BW_FLIP_D, watermarks->a.frac_urg_bw_flip);
315 0 : } else if (watermarks->a.frac_urg_bw_flip
316 : < hubbub1->watermarks.a.frac_urg_bw_flip)
317 0 : wm_pending = true;
318 :
319 0 : if (safe_to_lower || watermarks->a.frac_urg_bw_nom
320 0 : > hubbub1->watermarks.a.frac_urg_bw_nom) {
321 0 : hubbub1->watermarks.a.frac_urg_bw_nom = watermarks->a.frac_urg_bw_nom;
322 :
323 0 : REG_SET(DCHUBBUB_ARB_FRAC_URG_BW_NOM_D, 0,
324 : DCHUBBUB_ARB_FRAC_URG_BW_NOM_D, watermarks->a.frac_urg_bw_nom);
325 0 : } else if (watermarks->a.frac_urg_bw_nom
326 : < hubbub1->watermarks.a.frac_urg_bw_nom)
327 0 : wm_pending = true;
328 :
329 0 : if (safe_to_lower || watermarks->d.urgent_latency_ns > hubbub1->watermarks.d.urgent_latency_ns) {
330 0 : hubbub1->watermarks.d.urgent_latency_ns = watermarks->d.urgent_latency_ns;
331 0 : prog_wm_value = convert_and_clamp(watermarks->d.urgent_latency_ns,
332 : refclk_mhz, 0x1fffff);
333 0 : REG_SET(DCHUBBUB_ARB_REFCYC_PER_TRIP_TO_MEMORY_D, 0,
334 : DCHUBBUB_ARB_REFCYC_PER_TRIP_TO_MEMORY_D, prog_wm_value);
335 0 : } else if (watermarks->d.urgent_latency_ns < hubbub1->watermarks.d.urgent_latency_ns)
336 0 : wm_pending = true;
337 :
338 0 : return wm_pending;
339 : }
340 :
341 0 : bool hubbub21_program_stutter_watermarks(
342 : struct hubbub *hubbub,
343 : struct dcn_watermark_set *watermarks,
344 : unsigned int refclk_mhz,
345 : bool safe_to_lower)
346 : {
347 0 : struct dcn20_hubbub *hubbub1 = TO_DCN20_HUBBUB(hubbub);
348 : uint32_t prog_wm_value;
349 0 : bool wm_pending = false;
350 :
351 : /* clock state A */
352 0 : if (safe_to_lower || watermarks->a.cstate_pstate.cstate_enter_plus_exit_ns
353 0 : > hubbub1->watermarks.a.cstate_pstate.cstate_enter_plus_exit_ns) {
354 0 : hubbub1->watermarks.a.cstate_pstate.cstate_enter_plus_exit_ns =
355 0 : watermarks->a.cstate_pstate.cstate_enter_plus_exit_ns;
356 0 : prog_wm_value = convert_and_clamp(
357 : watermarks->a.cstate_pstate.cstate_enter_plus_exit_ns,
358 : refclk_mhz, 0x1fffff);
359 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_A, 0,
360 : DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_A, prog_wm_value,
361 : DCHUBBUB_ARB_VM_ROW_ALLOW_SR_ENTER_WATERMARK_A, prog_wm_value);
362 0 : DC_LOG_BANDWIDTH_CALCS("SR_ENTER_EXIT_WATERMARK_A calculated =%d\n"
363 : "HW register value = 0x%x\n",
364 : watermarks->a.cstate_pstate.cstate_enter_plus_exit_ns, prog_wm_value);
365 0 : } else if (watermarks->a.cstate_pstate.cstate_enter_plus_exit_ns
366 : < hubbub1->watermarks.a.cstate_pstate.cstate_enter_plus_exit_ns)
367 0 : wm_pending = true;
368 :
369 0 : if (safe_to_lower || watermarks->a.cstate_pstate.cstate_exit_ns
370 0 : > hubbub1->watermarks.a.cstate_pstate.cstate_exit_ns) {
371 0 : hubbub1->watermarks.a.cstate_pstate.cstate_exit_ns =
372 0 : watermarks->a.cstate_pstate.cstate_exit_ns;
373 0 : prog_wm_value = convert_and_clamp(
374 : watermarks->a.cstate_pstate.cstate_exit_ns,
375 : refclk_mhz, 0x1fffff);
376 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_A, 0,
377 : DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_A, prog_wm_value,
378 : DCHUBBUB_ARB_VM_ROW_ALLOW_SR_EXIT_WATERMARK_A, prog_wm_value);
379 0 : DC_LOG_BANDWIDTH_CALCS("SR_EXIT_WATERMARK_A calculated =%d\n"
380 : "HW register value = 0x%x\n",
381 : watermarks->a.cstate_pstate.cstate_exit_ns, prog_wm_value);
382 0 : } else if (watermarks->a.cstate_pstate.cstate_exit_ns
383 : < hubbub1->watermarks.a.cstate_pstate.cstate_exit_ns)
384 0 : wm_pending = true;
385 :
386 : /* clock state B */
387 0 : if (safe_to_lower || watermarks->b.cstate_pstate.cstate_enter_plus_exit_ns
388 0 : > hubbub1->watermarks.b.cstate_pstate.cstate_enter_plus_exit_ns) {
389 0 : hubbub1->watermarks.b.cstate_pstate.cstate_enter_plus_exit_ns =
390 0 : watermarks->b.cstate_pstate.cstate_enter_plus_exit_ns;
391 0 : prog_wm_value = convert_and_clamp(
392 : watermarks->b.cstate_pstate.cstate_enter_plus_exit_ns,
393 : refclk_mhz, 0x1fffff);
394 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_B, 0,
395 : DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_B, prog_wm_value,
396 : DCHUBBUB_ARB_VM_ROW_ALLOW_SR_ENTER_WATERMARK_B, prog_wm_value);
397 0 : DC_LOG_BANDWIDTH_CALCS("SR_ENTER_EXIT_WATERMARK_B calculated =%d\n"
398 : "HW register value = 0x%x\n",
399 : watermarks->b.cstate_pstate.cstate_enter_plus_exit_ns, prog_wm_value);
400 0 : } else if (watermarks->b.cstate_pstate.cstate_enter_plus_exit_ns
401 : < hubbub1->watermarks.b.cstate_pstate.cstate_enter_plus_exit_ns)
402 0 : wm_pending = true;
403 :
404 0 : if (safe_to_lower || watermarks->b.cstate_pstate.cstate_exit_ns
405 0 : > hubbub1->watermarks.b.cstate_pstate.cstate_exit_ns) {
406 0 : hubbub1->watermarks.b.cstate_pstate.cstate_exit_ns =
407 0 : watermarks->b.cstate_pstate.cstate_exit_ns;
408 0 : prog_wm_value = convert_and_clamp(
409 : watermarks->b.cstate_pstate.cstate_exit_ns,
410 : refclk_mhz, 0x1fffff);
411 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_B, 0,
412 : DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_B, prog_wm_value,
413 : DCHUBBUB_ARB_VM_ROW_ALLOW_SR_EXIT_WATERMARK_A, prog_wm_value);
414 0 : DC_LOG_BANDWIDTH_CALCS("SR_EXIT_WATERMARK_B calculated =%d\n"
415 : "HW register value = 0x%x\n",
416 : watermarks->b.cstate_pstate.cstate_exit_ns, prog_wm_value);
417 0 : } else if (watermarks->b.cstate_pstate.cstate_exit_ns
418 : < hubbub1->watermarks.b.cstate_pstate.cstate_exit_ns)
419 0 : wm_pending = true;
420 :
421 : /* clock state C */
422 0 : if (safe_to_lower || watermarks->c.cstate_pstate.cstate_enter_plus_exit_ns
423 0 : > hubbub1->watermarks.c.cstate_pstate.cstate_enter_plus_exit_ns) {
424 0 : hubbub1->watermarks.c.cstate_pstate.cstate_enter_plus_exit_ns =
425 0 : watermarks->c.cstate_pstate.cstate_enter_plus_exit_ns;
426 0 : prog_wm_value = convert_and_clamp(
427 : watermarks->c.cstate_pstate.cstate_enter_plus_exit_ns,
428 : refclk_mhz, 0x1fffff);
429 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_C, 0,
430 : DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_C, prog_wm_value,
431 : DCHUBBUB_ARB_VM_ROW_ALLOW_SR_ENTER_WATERMARK_C, prog_wm_value);
432 0 : DC_LOG_BANDWIDTH_CALCS("SR_ENTER_EXIT_WATERMARK_C calculated =%d\n"
433 : "HW register value = 0x%x\n",
434 : watermarks->c.cstate_pstate.cstate_enter_plus_exit_ns, prog_wm_value);
435 0 : } else if (watermarks->c.cstate_pstate.cstate_enter_plus_exit_ns
436 : < hubbub1->watermarks.c.cstate_pstate.cstate_enter_plus_exit_ns)
437 0 : wm_pending = true;
438 :
439 0 : if (safe_to_lower || watermarks->c.cstate_pstate.cstate_exit_ns
440 0 : > hubbub1->watermarks.c.cstate_pstate.cstate_exit_ns) {
441 0 : hubbub1->watermarks.c.cstate_pstate.cstate_exit_ns =
442 0 : watermarks->c.cstate_pstate.cstate_exit_ns;
443 0 : prog_wm_value = convert_and_clamp(
444 : watermarks->c.cstate_pstate.cstate_exit_ns,
445 : refclk_mhz, 0x1fffff);
446 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_C, 0,
447 : DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_C, prog_wm_value,
448 : DCHUBBUB_ARB_VM_ROW_ALLOW_SR_EXIT_WATERMARK_A, prog_wm_value);
449 0 : DC_LOG_BANDWIDTH_CALCS("SR_EXIT_WATERMARK_C calculated =%d\n"
450 : "HW register value = 0x%x\n",
451 : watermarks->c.cstate_pstate.cstate_exit_ns, prog_wm_value);
452 0 : } else if (watermarks->c.cstate_pstate.cstate_exit_ns
453 : < hubbub1->watermarks.c.cstate_pstate.cstate_exit_ns)
454 0 : wm_pending = true;
455 :
456 : /* clock state D */
457 0 : if (safe_to_lower || watermarks->d.cstate_pstate.cstate_enter_plus_exit_ns
458 0 : > hubbub1->watermarks.d.cstate_pstate.cstate_enter_plus_exit_ns) {
459 0 : hubbub1->watermarks.d.cstate_pstate.cstate_enter_plus_exit_ns =
460 0 : watermarks->d.cstate_pstate.cstate_enter_plus_exit_ns;
461 0 : prog_wm_value = convert_and_clamp(
462 : watermarks->d.cstate_pstate.cstate_enter_plus_exit_ns,
463 : refclk_mhz, 0x1fffff);
464 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_D, 0,
465 : DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_D, prog_wm_value,
466 : DCHUBBUB_ARB_VM_ROW_ALLOW_SR_ENTER_WATERMARK_D, prog_wm_value);
467 0 : DC_LOG_BANDWIDTH_CALCS("SR_ENTER_EXIT_WATERMARK_D calculated =%d\n"
468 : "HW register value = 0x%x\n",
469 : watermarks->d.cstate_pstate.cstate_enter_plus_exit_ns, prog_wm_value);
470 0 : } else if (watermarks->d.cstate_pstate.cstate_enter_plus_exit_ns
471 : < hubbub1->watermarks.d.cstate_pstate.cstate_enter_plus_exit_ns)
472 0 : wm_pending = true;
473 :
474 0 : if (safe_to_lower || watermarks->d.cstate_pstate.cstate_exit_ns
475 0 : > hubbub1->watermarks.d.cstate_pstate.cstate_exit_ns) {
476 0 : hubbub1->watermarks.d.cstate_pstate.cstate_exit_ns =
477 0 : watermarks->d.cstate_pstate.cstate_exit_ns;
478 0 : prog_wm_value = convert_and_clamp(
479 : watermarks->d.cstate_pstate.cstate_exit_ns,
480 : refclk_mhz, 0x1fffff);
481 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_D, 0,
482 : DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_D, prog_wm_value,
483 : DCHUBBUB_ARB_VM_ROW_ALLOW_SR_EXIT_WATERMARK_A, prog_wm_value);
484 0 : DC_LOG_BANDWIDTH_CALCS("SR_EXIT_WATERMARK_D calculated =%d\n"
485 : "HW register value = 0x%x\n",
486 : watermarks->d.cstate_pstate.cstate_exit_ns, prog_wm_value);
487 0 : } else if (watermarks->d.cstate_pstate.cstate_exit_ns
488 : < hubbub1->watermarks.d.cstate_pstate.cstate_exit_ns)
489 0 : wm_pending = true;
490 :
491 0 : return wm_pending;
492 : }
493 :
494 0 : bool hubbub21_program_pstate_watermarks(
495 : struct hubbub *hubbub,
496 : struct dcn_watermark_set *watermarks,
497 : unsigned int refclk_mhz,
498 : bool safe_to_lower)
499 : {
500 0 : struct dcn20_hubbub *hubbub1 = TO_DCN20_HUBBUB(hubbub);
501 : uint32_t prog_wm_value;
502 :
503 0 : bool wm_pending = false;
504 :
505 : /* clock state A */
506 0 : if (safe_to_lower || watermarks->a.cstate_pstate.pstate_change_ns
507 0 : > hubbub1->watermarks.a.cstate_pstate.pstate_change_ns) {
508 0 : hubbub1->watermarks.a.cstate_pstate.pstate_change_ns =
509 0 : watermarks->a.cstate_pstate.pstate_change_ns;
510 0 : prog_wm_value = convert_and_clamp(
511 : watermarks->a.cstate_pstate.pstate_change_ns,
512 : refclk_mhz, 0x1fffff);
513 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_A, 0,
514 : DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_A, prog_wm_value,
515 : DCHUBBUB_ARB_VM_ROW_ALLOW_DRAM_CLK_CHANGE_WATERMARK_A, prog_wm_value);
516 0 : DC_LOG_BANDWIDTH_CALCS("DRAM_CLK_CHANGE_WATERMARK_A calculated =%d\n"
517 : "HW register value = 0x%x\n\n",
518 : watermarks->a.cstate_pstate.pstate_change_ns, prog_wm_value);
519 0 : } else if (watermarks->a.cstate_pstate.pstate_change_ns
520 : < hubbub1->watermarks.a.cstate_pstate.pstate_change_ns)
521 0 : wm_pending = true;
522 :
523 : /* clock state B */
524 0 : if (safe_to_lower || watermarks->b.cstate_pstate.pstate_change_ns
525 0 : > hubbub1->watermarks.b.cstate_pstate.pstate_change_ns) {
526 0 : hubbub1->watermarks.b.cstate_pstate.pstate_change_ns =
527 0 : watermarks->b.cstate_pstate.pstate_change_ns;
528 0 : prog_wm_value = convert_and_clamp(
529 : watermarks->b.cstate_pstate.pstate_change_ns,
530 : refclk_mhz, 0x1fffff);
531 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_B, 0,
532 : DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_B, prog_wm_value,
533 : DCHUBBUB_ARB_VM_ROW_ALLOW_DRAM_CLK_CHANGE_WATERMARK_B, prog_wm_value);
534 0 : DC_LOG_BANDWIDTH_CALCS("DRAM_CLK_CHANGE_WATERMARK_B calculated =%d\n"
535 : "HW register value = 0x%x\n\n",
536 : watermarks->b.cstate_pstate.pstate_change_ns, prog_wm_value);
537 0 : } else if (watermarks->b.cstate_pstate.pstate_change_ns
538 : < hubbub1->watermarks.b.cstate_pstate.pstate_change_ns)
539 0 : wm_pending = false;
540 :
541 : /* clock state C */
542 0 : if (safe_to_lower || watermarks->c.cstate_pstate.pstate_change_ns
543 0 : > hubbub1->watermarks.c.cstate_pstate.pstate_change_ns) {
544 0 : hubbub1->watermarks.c.cstate_pstate.pstate_change_ns =
545 0 : watermarks->c.cstate_pstate.pstate_change_ns;
546 0 : prog_wm_value = convert_and_clamp(
547 : watermarks->c.cstate_pstate.pstate_change_ns,
548 : refclk_mhz, 0x1fffff);
549 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_C, 0,
550 : DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_C, prog_wm_value,
551 : DCHUBBUB_ARB_VM_ROW_ALLOW_DRAM_CLK_CHANGE_WATERMARK_C, prog_wm_value);
552 0 : DC_LOG_BANDWIDTH_CALCS("DRAM_CLK_CHANGE_WATERMARK_C calculated =%d\n"
553 : "HW register value = 0x%x\n\n",
554 : watermarks->c.cstate_pstate.pstate_change_ns, prog_wm_value);
555 0 : } else if (watermarks->c.cstate_pstate.pstate_change_ns
556 : < hubbub1->watermarks.c.cstate_pstate.pstate_change_ns)
557 0 : wm_pending = true;
558 :
559 : /* clock state D */
560 0 : if (safe_to_lower || watermarks->d.cstate_pstate.pstate_change_ns
561 0 : > hubbub1->watermarks.d.cstate_pstate.pstate_change_ns) {
562 0 : hubbub1->watermarks.d.cstate_pstate.pstate_change_ns =
563 0 : watermarks->d.cstate_pstate.pstate_change_ns;
564 0 : prog_wm_value = convert_and_clamp(
565 : watermarks->d.cstate_pstate.pstate_change_ns,
566 : refclk_mhz, 0x1fffff);
567 0 : REG_SET_2(DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_D, 0,
568 : DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_D, prog_wm_value,
569 : DCHUBBUB_ARB_VM_ROW_ALLOW_DRAM_CLK_CHANGE_WATERMARK_D, prog_wm_value);
570 0 : DC_LOG_BANDWIDTH_CALCS("DRAM_CLK_CHANGE_WATERMARK_D calculated =%d\n"
571 : "HW register value = 0x%x\n\n",
572 : watermarks->d.cstate_pstate.pstate_change_ns, prog_wm_value);
573 0 : } else if (watermarks->d.cstate_pstate.pstate_change_ns
574 : < hubbub1->watermarks.d.cstate_pstate.pstate_change_ns)
575 0 : wm_pending = true;
576 :
577 0 : return wm_pending;
578 : }
579 :
580 0 : bool hubbub21_program_watermarks(
581 : struct hubbub *hubbub,
582 : struct dcn_watermark_set *watermarks,
583 : unsigned int refclk_mhz,
584 : bool safe_to_lower)
585 : {
586 0 : struct dcn20_hubbub *hubbub1 = TO_DCN20_HUBBUB(hubbub);
587 0 : bool wm_pending = false;
588 :
589 0 : if (hubbub21_program_urgent_watermarks(hubbub, watermarks, refclk_mhz, safe_to_lower))
590 0 : wm_pending = true;
591 :
592 0 : if (hubbub21_program_stutter_watermarks(hubbub, watermarks, refclk_mhz, safe_to_lower))
593 0 : wm_pending = true;
594 :
595 0 : if (hubbub21_program_pstate_watermarks(hubbub, watermarks, refclk_mhz, safe_to_lower))
596 0 : wm_pending = true;
597 :
598 : /*
599 : * The DCHub arbiter has a mechanism to dynamically rate limit the DCHub request stream to the fabric.
600 : * If the memory controller is fully utilized and the DCHub requestors are
601 : * well ahead of their amortized schedule, then it is safe to prevent the next winner
602 : * from being committed and sent to the fabric.
603 : * The utilization of the memory controller is approximated by ensuring that
604 : * the number of outstanding requests is greater than a threshold specified
605 : * by the ARB_MIN_REQ_OUTSTANDING. To determine that the DCHub requestors are well ahead of the amortized schedule,
606 : * the slack of the next winner is compared with the ARB_SAT_LEVEL in DLG RefClk cycles.
607 : *
608 : * TODO: Revisit request limit after figure out right number. request limit for Renoir isn't decided yet, set maximum value (0x1FF)
609 : * to turn off it for now.
610 : */
611 0 : REG_SET(DCHUBBUB_ARB_SAT_LEVEL, 0,
612 : DCHUBBUB_ARB_SAT_LEVEL, 60 * refclk_mhz);
613 0 : REG_UPDATE_2(DCHUBBUB_ARB_DF_REQ_OUTSTAND,
614 : DCHUBBUB_ARB_MIN_REQ_OUTSTAND, 0x1FF,
615 : DCHUBBUB_ARB_MIN_REQ_OUTSTAND_COMMIT_THRESHOLD, 0xA);
616 0 : REG_UPDATE(DCHUBBUB_ARB_HOSTVM_CNTL,
617 : DCHUBBUB_ARB_MAX_QOS_COMMIT_THRESHOLD, 0xF);
618 :
619 0 : hubbub1_allow_self_refresh_control(hubbub, !hubbub->ctx->dc->debug.disable_stutter);
620 :
621 0 : return wm_pending;
622 : }
623 :
624 0 : void hubbub21_wm_read_state(struct hubbub *hubbub,
625 : struct dcn_hubbub_wm *wm)
626 : {
627 0 : struct dcn20_hubbub *hubbub1 = TO_DCN20_HUBBUB(hubbub);
628 : struct dcn_hubbub_wm_set *s;
629 :
630 0 : memset(wm, 0, sizeof(struct dcn_hubbub_wm));
631 :
632 0 : s = &wm->sets[0];
633 0 : s->wm_set = 0;
634 0 : REG_GET(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_A,
635 : DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_A, &s->data_urgent);
636 :
637 0 : REG_GET(DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_A,
638 : DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_A, &s->sr_enter);
639 :
640 0 : REG_GET(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_A,
641 : DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_A, &s->sr_exit);
642 :
643 0 : REG_GET(DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_A,
644 : DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_A, &s->dram_clk_chanage);
645 :
646 0 : s = &wm->sets[1];
647 0 : s->wm_set = 1;
648 0 : REG_GET(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_B,
649 : DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_B, &s->data_urgent);
650 :
651 0 : REG_GET(DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_B,
652 : DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_B, &s->sr_enter);
653 :
654 0 : REG_GET(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_B,
655 : DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_B, &s->sr_exit);
656 :
657 0 : REG_GET(DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_B,
658 : DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_B, &s->dram_clk_chanage);
659 :
660 0 : s = &wm->sets[2];
661 0 : s->wm_set = 2;
662 0 : REG_GET(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_C,
663 : DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_C, &s->data_urgent);
664 :
665 0 : REG_GET(DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_C,
666 : DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_C, &s->sr_enter);
667 :
668 0 : REG_GET(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_C,
669 : DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_C, &s->sr_exit);
670 :
671 0 : REG_GET(DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_C,
672 : DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_C, &s->dram_clk_chanage);
673 :
674 0 : s = &wm->sets[3];
675 0 : s->wm_set = 3;
676 0 : REG_GET(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_D,
677 : DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_D, &s->data_urgent);
678 :
679 0 : REG_GET(DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_D,
680 : DCHUBBUB_ARB_ALLOW_SR_ENTER_WATERMARK_D, &s->sr_enter);
681 :
682 0 : REG_GET(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_D,
683 : DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_D, &s->sr_exit);
684 :
685 0 : REG_GET(DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_D,
686 : DCHUBBUB_ARB_ALLOW_DRAM_CLK_CHANGE_WATERMARK_D, &s->dram_clk_chanage);
687 0 : }
688 :
689 0 : static void hubbub21_apply_DEDCN21_147_wa(struct hubbub *hubbub)
690 : {
691 0 : struct dcn20_hubbub *hubbub1 = TO_DCN20_HUBBUB(hubbub);
692 : uint32_t prog_wm_value;
693 :
694 0 : prog_wm_value = REG_READ(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_A);
695 0 : REG_WRITE(DCHUBBUB_ARB_DATA_URGENCY_WATERMARK_A, prog_wm_value);
696 0 : }
697 :
698 : static const struct hubbub_funcs hubbub21_funcs = {
699 : .update_dchub = hubbub2_update_dchub,
700 : .init_dchub_sys_ctx = hubbub21_init_dchub,
701 : .init_vm_ctx = hubbub2_init_vm_ctx,
702 : .dcc_support_swizzle = hubbub2_dcc_support_swizzle,
703 : .dcc_support_pixel_format = hubbub2_dcc_support_pixel_format,
704 : .get_dcc_compression_cap = hubbub2_get_dcc_compression_cap,
705 : .wm_read_state = hubbub21_wm_read_state,
706 : .get_dchub_ref_freq = hubbub2_get_dchub_ref_freq,
707 : .program_watermarks = hubbub21_program_watermarks,
708 : .allow_self_refresh_control = hubbub1_allow_self_refresh_control,
709 : .apply_DEDCN21_147_wa = hubbub21_apply_DEDCN21_147_wa,
710 : .hubbub_read_state = hubbub2_read_state,
711 : };
712 :
713 0 : void hubbub21_construct(struct dcn20_hubbub *hubbub,
714 : struct dc_context *ctx,
715 : const struct dcn_hubbub_registers *hubbub_regs,
716 : const struct dcn_hubbub_shift *hubbub_shift,
717 : const struct dcn_hubbub_mask *hubbub_mask)
718 : {
719 0 : hubbub->base.ctx = ctx;
720 :
721 0 : hubbub->base.funcs = &hubbub21_funcs;
722 :
723 0 : hubbub->regs = hubbub_regs;
724 0 : hubbub->shifts = hubbub_shift;
725 0 : hubbub->masks = hubbub_mask;
726 :
727 0 : hubbub->debug_test_index_pstate = 0xB;
728 0 : hubbub->detile_buf_size = 164 * 1024; /* 164KB for DCN2.0 */
729 0 : }
|