diff options
author | Christian König <deathsimple@vodafone.de> | 2013-04-08 06:41:34 -0400 |
---|---|---|
committer | Alex Deucher <alexander.deucher@amd.com> | 2013-04-09 10:31:37 -0400 |
commit | 2539eb02de42f2bc60f329e3adb75d41697089df (patch) | |
tree | 6359d7d2af6c9974dcd6df3f240c6b5c853d0928 /drivers/gpu/drm/radeon/si.c | |
parent | a8b4925c79c804055e50515177dbc47909396c95 (diff) |
drm/radeon: add set_uvd_clocks callback for SI
Signed-off-by: Christian König <christian.koenig@amd.com>
Reviewed-by: Jerome Glisse <jglisse@redhat.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Diffstat (limited to 'drivers/gpu/drm/radeon/si.c')
-rw-r--r-- | drivers/gpu/drm/radeon/si.c | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/drivers/gpu/drm/radeon/si.c b/drivers/gpu/drm/radeon/si.c index 3e9782dc35bf..465053d461bb 100644 --- a/drivers/gpu/drm/radeon/si.c +++ b/drivers/gpu/drm/radeon/si.c | |||
@@ -4627,3 +4627,170 @@ uint64_t si_get_gpu_clock_counter(struct radeon_device *rdev) | |||
4627 | mutex_unlock(&rdev->gpu_clock_mutex); | 4627 | mutex_unlock(&rdev->gpu_clock_mutex); |
4628 | return clock; | 4628 | return clock; |
4629 | } | 4629 | } |
4630 | |||
4631 | static int si_uvd_calc_post_div(unsigned target_freq, | ||
4632 | unsigned vco_freq, | ||
4633 | unsigned *div) | ||
4634 | { | ||
4635 | /* target larger than vco frequency ? */ | ||
4636 | if (vco_freq < target_freq) | ||
4637 | return -1; /* forget it */ | ||
4638 | |||
4639 | /* Fclk = Fvco / PDIV */ | ||
4640 | *div = vco_freq / target_freq; | ||
4641 | |||
4642 | /* we alway need a frequency less than or equal the target */ | ||
4643 | if ((vco_freq / *div) > target_freq) | ||
4644 | *div += 1; | ||
4645 | |||
4646 | /* dividers above 5 must be even */ | ||
4647 | if (*div > 5 && *div % 2) | ||
4648 | *div += 1; | ||
4649 | |||
4650 | /* out of range ? */ | ||
4651 | if (*div >= 128) | ||
4652 | return -1; /* forget it */ | ||
4653 | |||
4654 | return vco_freq / *div; | ||
4655 | } | ||
4656 | |||
4657 | static int si_uvd_send_upll_ctlreq(struct radeon_device *rdev) | ||
4658 | { | ||
4659 | unsigned i; | ||
4660 | |||
4661 | /* assert UPLL_CTLREQ */ | ||
4662 | WREG32_P(CG_UPLL_FUNC_CNTL, UPLL_CTLREQ_MASK, ~UPLL_CTLREQ_MASK); | ||
4663 | |||
4664 | /* wait for CTLACK and CTLACK2 to get asserted */ | ||
4665 | for (i = 0; i < 100; ++i) { | ||
4666 | uint32_t mask = UPLL_CTLACK_MASK | UPLL_CTLACK2_MASK; | ||
4667 | if ((RREG32(CG_UPLL_FUNC_CNTL) & mask) == mask) | ||
4668 | break; | ||
4669 | mdelay(10); | ||
4670 | } | ||
4671 | if (i == 100) | ||
4672 | return -ETIMEDOUT; | ||
4673 | |||
4674 | /* deassert UPLL_CTLREQ */ | ||
4675 | WREG32_P(CG_UPLL_FUNC_CNTL, 0, ~UPLL_CTLREQ_MASK); | ||
4676 | |||
4677 | return 0; | ||
4678 | } | ||
4679 | |||
4680 | int si_set_uvd_clocks(struct radeon_device *rdev, u32 vclk, u32 dclk) | ||
4681 | { | ||
4682 | /* start off with something large */ | ||
4683 | int optimal_diff_score = 0x7FFFFFF; | ||
4684 | unsigned optimal_fb_div = 0, optimal_vclk_div = 0; | ||
4685 | unsigned optimal_dclk_div = 0, optimal_vco_freq = 0; | ||
4686 | unsigned vco_freq; | ||
4687 | int r; | ||
4688 | |||
4689 | /* loop through vco from low to high */ | ||
4690 | for (vco_freq = 125000; vco_freq <= 250000; vco_freq += 100) { | ||
4691 | unsigned fb_div = vco_freq / rdev->clock.spll.reference_freq * 16384; | ||
4692 | int calc_clk, diff_score, diff_vclk, diff_dclk; | ||
4693 | unsigned vclk_div, dclk_div; | ||
4694 | |||
4695 | /* fb div out of range ? */ | ||
4696 | if (fb_div > 0x03FFFFFF) | ||
4697 | break; /* it can oly get worse */ | ||
4698 | |||
4699 | /* calc vclk with current vco freq. */ | ||
4700 | calc_clk = si_uvd_calc_post_div(vclk, vco_freq, &vclk_div); | ||
4701 | if (calc_clk == -1) | ||
4702 | break; /* vco is too big, it has to stop. */ | ||
4703 | diff_vclk = vclk - calc_clk; | ||
4704 | |||
4705 | /* calc dclk with current vco freq. */ | ||
4706 | calc_clk = si_uvd_calc_post_div(dclk, vco_freq, &dclk_div); | ||
4707 | if (calc_clk == -1) | ||
4708 | break; /* vco is too big, it has to stop. */ | ||
4709 | diff_dclk = dclk - calc_clk; | ||
4710 | |||
4711 | /* determine if this vco setting is better than current optimal settings */ | ||
4712 | diff_score = abs(diff_vclk) + abs(diff_dclk); | ||
4713 | if (diff_score < optimal_diff_score) { | ||
4714 | optimal_fb_div = fb_div; | ||
4715 | optimal_vclk_div = vclk_div; | ||
4716 | optimal_dclk_div = dclk_div; | ||
4717 | optimal_vco_freq = vco_freq; | ||
4718 | optimal_diff_score = diff_score; | ||
4719 | if (optimal_diff_score == 0) | ||
4720 | break; /* it can't get better than this */ | ||
4721 | } | ||
4722 | } | ||
4723 | |||
4724 | /* set RESET_ANTI_MUX to 0 */ | ||
4725 | WREG32_P(CG_UPLL_FUNC_CNTL_5, 0, ~RESET_ANTI_MUX_MASK); | ||
4726 | |||
4727 | /* set VCO_MODE to 1 */ | ||
4728 | WREG32_P(CG_UPLL_FUNC_CNTL, UPLL_VCO_MODE_MASK, ~UPLL_VCO_MODE_MASK); | ||
4729 | |||
4730 | /* toggle UPLL_SLEEP to 1 then back to 0 */ | ||
4731 | WREG32_P(CG_UPLL_FUNC_CNTL, UPLL_SLEEP_MASK, ~UPLL_SLEEP_MASK); | ||
4732 | WREG32_P(CG_UPLL_FUNC_CNTL, 0, ~UPLL_SLEEP_MASK); | ||
4733 | |||
4734 | /* deassert UPLL_RESET */ | ||
4735 | WREG32_P(CG_UPLL_FUNC_CNTL, 0, ~UPLL_RESET_MASK); | ||
4736 | |||
4737 | mdelay(1); | ||
4738 | |||
4739 | /* bypass vclk and dclk with bclk */ | ||
4740 | WREG32_P(CG_UPLL_FUNC_CNTL_2, | ||
4741 | VCLK_SRC_SEL(1) | DCLK_SRC_SEL(1), | ||
4742 | ~(VCLK_SRC_SEL_MASK | DCLK_SRC_SEL_MASK)); | ||
4743 | |||
4744 | /* put PLL in bypass mode */ | ||
4745 | WREG32_P(CG_UPLL_FUNC_CNTL, UPLL_BYPASS_EN_MASK, ~UPLL_BYPASS_EN_MASK); | ||
4746 | |||
4747 | r = si_uvd_send_upll_ctlreq(rdev); | ||
4748 | if (r) | ||
4749 | return r; | ||
4750 | |||
4751 | /* assert UPLL_RESET again */ | ||
4752 | WREG32_P(CG_UPLL_FUNC_CNTL, UPLL_RESET_MASK, ~UPLL_RESET_MASK); | ||
4753 | |||
4754 | /* disable spread spectrum. */ | ||
4755 | WREG32_P(CG_UPLL_SPREAD_SPECTRUM, 0, ~SSEN_MASK); | ||
4756 | |||
4757 | /* set feedback divider */ | ||
4758 | WREG32_P(CG_UPLL_FUNC_CNTL_3, UPLL_FB_DIV(optimal_fb_div), ~UPLL_FB_DIV_MASK); | ||
4759 | |||
4760 | /* set ref divider to 0 */ | ||
4761 | WREG32_P(CG_UPLL_FUNC_CNTL, 0, ~UPLL_REF_DIV_MASK); | ||
4762 | |||
4763 | if (optimal_vco_freq < 187500) | ||
4764 | WREG32_P(CG_UPLL_FUNC_CNTL_4, 0, ~UPLL_SPARE_ISPARE9); | ||
4765 | else | ||
4766 | WREG32_P(CG_UPLL_FUNC_CNTL_4, UPLL_SPARE_ISPARE9, ~UPLL_SPARE_ISPARE9); | ||
4767 | |||
4768 | /* set PDIV_A and PDIV_B */ | ||
4769 | WREG32_P(CG_UPLL_FUNC_CNTL_2, | ||
4770 | UPLL_PDIV_A(optimal_vclk_div) | UPLL_PDIV_B(optimal_dclk_div), | ||
4771 | ~(UPLL_PDIV_A_MASK | UPLL_PDIV_B_MASK)); | ||
4772 | |||
4773 | /* give the PLL some time to settle */ | ||
4774 | mdelay(15); | ||
4775 | |||
4776 | /* deassert PLL_RESET */ | ||
4777 | WREG32_P(CG_UPLL_FUNC_CNTL, 0, ~UPLL_RESET_MASK); | ||
4778 | |||
4779 | mdelay(15); | ||
4780 | |||
4781 | /* switch from bypass mode to normal mode */ | ||
4782 | WREG32_P(CG_UPLL_FUNC_CNTL, 0, ~UPLL_BYPASS_EN_MASK); | ||
4783 | |||
4784 | r = si_uvd_send_upll_ctlreq(rdev); | ||
4785 | if (r) | ||
4786 | return r; | ||
4787 | |||
4788 | /* switch VCLK and DCLK selection */ | ||
4789 | WREG32_P(CG_UPLL_FUNC_CNTL_2, | ||
4790 | VCLK_SRC_SEL(2) | DCLK_SRC_SEL(2), | ||
4791 | ~(VCLK_SRC_SEL_MASK | DCLK_SRC_SEL_MASK)); | ||
4792 | |||
4793 | mdelay(100); | ||
4794 | |||
4795 | return 0; | ||
4796 | } | ||