summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/nvgpu/gm20b/clk_gm20b.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/nvgpu/gm20b/clk_gm20b.c')
-rw-r--r--drivers/gpu/nvgpu/gm20b/clk_gm20b.c93
1 files changed, 57 insertions, 36 deletions
diff --git a/drivers/gpu/nvgpu/gm20b/clk_gm20b.c b/drivers/gpu/nvgpu/gm20b/clk_gm20b.c
index a1614a7a..2e3d96ac 100644
--- a/drivers/gpu/nvgpu/gm20b/clk_gm20b.c
+++ b/drivers/gpu/nvgpu/gm20b/clk_gm20b.c
@@ -77,8 +77,26 @@ static inline u32 div_to_pl(u32 div)
77 return div; 77 return div;
78} 78}
79 79
80/* FIXME: remove after on-silicon testing */ 80#define PLDIV_GLITCHLESS 1
81#define PLDIV_GLITCHLESS 0 81
82#if PLDIV_GLITCHLESS
83/*
84 * Post divider tarnsition is glitchless only if there is common "1" in binary
85 * representation of old and new settings.
86 */
87static u32 get_interim_pldiv(u32 old_pl, u32 new_pl)
88{
89 u32 pl;
90
91 if (old_pl & new_pl)
92 return 0;
93
94 pl = old_pl | BIT(ffs(new_pl) - 1); /* pl never 0 */
95 new_pl |= BIT(ffs(old_pl) - 1);
96
97 return min(pl, new_pl);
98}
99#endif
82 100
83/* Calculate and update M/N/PL as well as pll->freq 101/* Calculate and update M/N/PL as well as pll->freq
84 ref_clk_f = clk_in_f; 102 ref_clk_f = clk_in_f;
@@ -419,10 +437,7 @@ pll_locked:
419static int clk_program_gpc_pll(struct gk20a *g, struct pll *gpll_new, 437static int clk_program_gpc_pll(struct gk20a *g, struct pll *gpll_new,
420 int allow_slide) 438 int allow_slide)
421{ 439{
422#if !PLDIV_GLITCHLESS 440 u32 cfg, coeff, data;
423 u32 data;
424#endif
425 u32 cfg, coeff;
426 bool can_slide, pldiv_only; 441 bool can_slide, pldiv_only;
427 struct pll gpll; 442 struct pll gpll;
428 443
@@ -456,50 +471,51 @@ static int clk_program_gpc_pll(struct gk20a *g, struct pll *gpll_new,
456 } 471 }
457 pldiv_only = can_slide && (gpll_new->M == gpll.M); 472 pldiv_only = can_slide && (gpll_new->M == gpll.M);
458 473
459#if PLDIV_GLITCHLESS
460 /* 474 /*
461 * Limit either FO-to-FO (path A below) or FO-to-bypass (path B below) 475 * Split FO-to-bypass jump in halfs by setting out divider 1:2.
462 * jump to min_vco/2 by setting post divider >= 1:2. 476 * (needed even if PLDIV_GLITCHLESS is set, since 1:1 <=> 1:2 direct
477 * transition is not really glitch-less - see get_interim_pldiv
478 * function header).
463 */ 479 */
480 if ((gpll_new->PL < 2) || (gpll.PL < 2)) {
481 data = gk20a_readl(g, trim_sys_gpc2clk_out_r());
482 data = set_field(data, trim_sys_gpc2clk_out_vcodiv_m(),
483 trim_sys_gpc2clk_out_vcodiv_f(2));
484 gk20a_writel(g, trim_sys_gpc2clk_out_r(), data);
485 /* Intentional 2nd write to assure linear divider operation */
486 gk20a_writel(g, trim_sys_gpc2clk_out_r(), data);
487 gk20a_readl(g, trim_sys_gpc2clk_out_r());
488 udelay(2);
489 }
490
491#if PLDIV_GLITCHLESS
464 coeff = gk20a_readl(g, trim_sys_gpcpll_coeff_r()); 492 coeff = gk20a_readl(g, trim_sys_gpcpll_coeff_r());
465 if ((pldiv_only && (gpll_new->PL < 2)) || (gpll.PL < 2)) { 493 if (pldiv_only) {
466 if (gpll.PL != 2) { 494 /* Insert interim PLDIV state if necessary */
495 u32 interim_pl = get_interim_pldiv(gpll_new->PL, gpll.PL);
496 if (interim_pl) {
467 coeff = set_field(coeff, 497 coeff = set_field(coeff,
468 trim_sys_gpcpll_coeff_pldiv_m(), 498 trim_sys_gpcpll_coeff_pldiv_m(),
469 trim_sys_gpcpll_coeff_pldiv_f(2)); 499 trim_sys_gpcpll_coeff_pldiv_f(interim_pl));
470 gk20a_writel(g, trim_sys_gpcpll_coeff_r(), coeff); 500 gk20a_writel(g, trim_sys_gpcpll_coeff_r(), coeff);
471 coeff = gk20a_readl(g, trim_sys_gpcpll_coeff_r()); 501 coeff = gk20a_readl(g, trim_sys_gpcpll_coeff_r());
472 udelay(2);
473 } 502 }
474 }
475
476 if (pldiv_only)
477 goto set_pldiv; /* path A: no need to bypass */ 503 goto set_pldiv; /* path A: no need to bypass */
504 }
478 505
479 /* path B: bypass if either M changes or PLL is disabled */ 506 /* path B: bypass if either M changes or PLL is disabled */
480#else
481 /* split FO-to-bypass jump in halfs by setting out divider 1:2 */
482 data = gk20a_readl(g, trim_sys_gpc2clk_out_r());
483 data = set_field(data, trim_sys_gpc2clk_out_vcodiv_m(),
484 trim_sys_gpc2clk_out_vcodiv_f(2));
485 gk20a_writel(g, trim_sys_gpc2clk_out_r(), data);
486 gk20a_readl(g, trim_sys_gpc2clk_out_r());
487 udelay(2);
488#endif 507#endif
489 /* 508 /*
490 * Program and lock pll under bypass. On exit PLL is out of bypass, 509 * Program and lock pll under bypass. On exit PLL is out of bypass,
491 * enabled, and locked. VCO is at vco_min if sliding is allowed. 510 * enabled, and locked. VCO is at vco_min if sliding is allowed.
492 * Otherwise it is at VCO target (and therefore last slide call below 511 * Otherwise it is at VCO target (and therefore last slide call below
493 * is effectively NOP). PL is preserved (not set to target) of post 512 * is effectively NOP). PL is set to target. Output divider is engaged
494 * divider is glitchless. Otherwise it is at PL target. 513 * at 1:2 if either entry, or exit PL setting is 1:1.
495 */ 514 */
496 gpll = *gpll_new; 515 gpll = *gpll_new;
497 if (allow_slide) 516 if (allow_slide)
498 gpll.N = DIV_ROUND_UP(gpll_new->M * gpc_pll_params.min_vco, 517 gpll.N = DIV_ROUND_UP(gpll_new->M * gpc_pll_params.min_vco,
499 gpll_new->clk_in); 518 gpll_new->clk_in);
500#if PLDIV_GLITCHLESS
501 gpll.PL = (gpll_new->PL < 2) ? 2 : gpll_new->PL;
502#endif
503 if (pldiv_only) 519 if (pldiv_only)
504 clk_change_pldiv_under_bypass(g, &gpll); 520 clk_change_pldiv_under_bypass(g, &gpll);
505 else 521 else
@@ -507,7 +523,6 @@ static int clk_program_gpc_pll(struct gk20a *g, struct pll *gpll_new,
507 523
508#if PLDIV_GLITCHLESS 524#if PLDIV_GLITCHLESS
509 coeff = gk20a_readl(g, trim_sys_gpcpll_coeff_r()); 525 coeff = gk20a_readl(g, trim_sys_gpcpll_coeff_r());
510 udelay(2);
511 526
512set_pldiv: 527set_pldiv:
513 /* coeff must be current from either path A or B */ 528 /* coeff must be current from either path A or B */
@@ -516,14 +531,20 @@ set_pldiv:
516 trim_sys_gpcpll_coeff_pldiv_f(gpll_new->PL)); 531 trim_sys_gpcpll_coeff_pldiv_f(gpll_new->PL));
517 gk20a_writel(g, trim_sys_gpcpll_coeff_r(), coeff); 532 gk20a_writel(g, trim_sys_gpcpll_coeff_r(), coeff);
518 } 533 }
519#else 534#endif
520 /* restore out divider 1:1 */ 535 /* restore out divider 1:1 */
521 data = gk20a_readl(g, trim_sys_gpc2clk_out_r()); 536 data = gk20a_readl(g, trim_sys_gpc2clk_out_r());
522 data = set_field(data, trim_sys_gpc2clk_out_vcodiv_m(), 537 if ((data & trim_sys_gpc2clk_out_vcodiv_m()) !=
523 trim_sys_gpc2clk_out_vcodiv_by1_f()); 538 trim_sys_gpc2clk_out_vcodiv_by1_f()) {
524 udelay(2); 539 data = set_field(data, trim_sys_gpc2clk_out_vcodiv_m(),
525 gk20a_writel(g, trim_sys_gpc2clk_out_r(), data); 540 trim_sys_gpc2clk_out_vcodiv_by1_f());
526#endif 541 udelay(2);
542 gk20a_writel(g, trim_sys_gpc2clk_out_r(), data);
543 /* Intentional 2nd write to assure linear divider operation */
544 gk20a_writel(g, trim_sys_gpc2clk_out_r(), data);
545 gk20a_readl(g, trim_sys_gpc2clk_out_r());
546 }
547
527 /* slide up to target NDIV */ 548 /* slide up to target NDIV */
528 return clk_slide_gpc_pll(g, gpll_new); 549 return clk_slide_gpc_pll(g, gpll_new);
529} 550}