/*
 * Copyright 2008 Advanced Micro Devices, Inc.
 * Copyright 2008 Red Hat Inc.
 * Copyright 2009 Jerome Glisse.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: Dave Airlie
 *          Alex Deucher
 *          Jerome Glisse
 */
#include "drmP.h"
#include "radeon_drm.h"
#include "radeon_reg.h"
#include "radeon.h"
#include "atom.h"

/* 10 khz */
uint32_t radeon_legacy_get_engine_clock(struct radeon_device *rdev)
{
	struct radeon_pll *spll = &rdev->clock.spll;
	uint32_t fb_div, ref_div, post_div, sclk;

	fb_div = RREG32_PLL(RADEON_M_SPLL_REF_FB_DIV);
	fb_div = (fb_div >> RADEON_SPLL_FB_DIV_SHIFT) & RADEON_SPLL_FB_DIV_MASK;
	fb_div <<= 1;
	fb_div *= spll->reference_freq;

	ref_div =
	    RREG32_PLL(RADEON_M_SPLL_REF_FB_DIV) & RADEON_M_SPLL_REF_DIV_MASK;

	if (ref_div == 0)
		return 0;

	sclk = fb_div / ref_div;

	post_div = RREG32_PLL(RADEON_SCLK_CNTL) & RADEON_SCLK_SRC_SEL_MASK;
	if (post_div == 2)
		sclk >>= 1;
	else if (post_div == 3)
		sclk >>= 2;
	else if (post_div == 4)
		sclk >>= 3;

	return sclk;
}

/* 10 khz */
uint32_t radeon_legacy_get_memory_clock(struct radeon_device *rdev)
{
	struct radeon_pll *mpll = &rdev->clock.mpll;
	uint32_t fb_div, ref_div, post_div, mclk;

	fb_div = RREG32_PLL(RADEON_M_SPLL_REF_FB_DIV);
	fb_div = (fb_div >> RADEON_MPLL_FB_DIV_SHIFT) & RADEON_MPLL_FB_DIV_MASK;
	fb_div <<= 1;
	fb_div *= mpll->reference_freq;

	ref_div =
	    RREG32_PLL(RADEON_M_SPLL_REF_FB_DIV) & RADEON_M_SPLL_REF_DIV_MASK;

	if (ref_div == 0)
		return 0;

	mclk = fb_div / ref_div;

	post_div = RREG32_PLL(RADEON_MCLK_CNTL) & 0x7;
	if (post_div == 2)
		mclk >>= 1;
	else if (post_div == 3)
		mclk >>= 2;
	else if (post_div == 4)
		mclk >>= 3;

	return mclk;
}

void radeon_get_clock_info(struct drm_device *dev)
{
	struct radeon_device *rdev = dev->dev_private;
	struct radeon_pll *p1pll = &rdev->clock.p1pll;
	struct radeon_pll *p2pll = &rdev->clock.p2pll;
	struct radeon_pll *spll = &rdev->clock.spll;
	struct radeon_pll *mpll = &rdev->clock.mpll;
	int ret;

	if (rdev->is_atom_bios)
		ret = radeon_atom_get_clock_info(dev);
	else
		ret = radeon_combios_get_clock_info(dev);

	if (ret) {
		if (p1pll->reference_div < 2) {
			if (!ASIC_IS_AVIVO(rdev)) {
				u32 tmp = RREG32_PLL(RADEON_PPLL_REF_DIV);
				if (ASIC_IS_R300(rdev))
					p1pll->reference_div =
						(tmp & R300_PPLL_REF_DIV_ACC_MASK) >> R300_PPLL_REF_DIV_ACC_SHIFT;
				else
					p1pll->reference_div = tmp & RADEON_PPLL_REF_DIV_MASK;
				if (p1pll->reference_div < 2)
					p1pll->reference_div = 12;
			} else
				p1pll->reference_div = 12;
		}
		if (p2pll->reference_div < 2)
			p2pll->reference_div = 12;
		if (rdev->family < CHIP_RS600) {
			if (spll->reference_div < 2)
				spll->reference_div =
					RREG32_PLL(RADEON_M_SPLL_REF_FB_DIV) &
					RADEON_M_SPLL_REF_DIV_MASK;
		}
		if (mpll->reference_div < 2)
			mpll->reference_div = spll->reference_div;
	} else {
		if (ASIC_IS_AVIVO(rdev)) {
			/* TODO FALLBACK */
		} else {
			DRM_INFO("Using generic clock info\n");

			if (rdev->flags & RADEON_IS_IGP) {
				p1pll->reference_freq = 1432;
				p2pll->reference_freq = 1432;
				spll->reference_freq = 1432;
				mpll->reference_freq = 1432;
			} else {
				p1pll->reference_freq = 2700;
				p2pll->reference_freq = 2700;
				spll->reference_freq = 2700;
				mpll->reference_freq = 2700;
			}
			p1pll->reference_div =
			    RREG32_PLL(RADEON_PPLL_REF_DIV) & 0x3ff;
			if (p1pll->reference_div < 2)
				p1pll->reference_div = 12;
			p2pll->reference_div = p1pll->reference_div;

			if (rdev->family >= CHIP_R420) {
				p1pll->pll_in_min = 100;
				p1pll->pll_in_max = 1350;
				p1pll->pll_out_min = 20000;
				p1pll->pll_out_max = 50000;
				p2pll->pll_in_min = 100;
				p2pll->pll_in_max = 1350;
				p2pll->pll_out_min = 20000;
				p2pll->pll_out_max = 50000;
			} else {
				p1pll->pll_in_min = 40;
				p1pll->pll_in_max = 500;
				p1pll->pll_out_min = 12500;
				p1pll->pll_out_max = 35000;
				p2pll->pll_in_min = 40;
				p2pll->pll_in_max = 500;
				p2pll->pll_out_min = 12500;
				p2pll->pll_out_max = 35000;
			}

			spll->reference_div =
			    RREG32_PLL(RADEON_M_SPLL_REF_FB_DIV) &
			    RADEON_M_SPLL_REF_DIV_MASK;
			mpll->reference_div = spll->reference_div;
			rdev->clock.default_sclk =
			    radeon_legacy_get_engine_clock(rdev);
			rdev->clock.default_mclk =
			    radeon_legacy_get_memory_clock(rdev);
		}
	}

	/* pixel clocks */
	if (ASIC_IS_AVIVO(rdev)) {
		p1pll->min_post_div = 2;
		p1pll->max_post_div = 0x7f;
		p1pll->min_frac_feedback_div = 0;
		p1pll->max_frac_feedback_div = 9;
		p2pll->min_post_div = 2;
		p2pll->max_post_div = 0x7f;
		p2pll->min_frac_feedback_div = 0;
		p2pll->max_frac_feedback_div = 9;
	} else {
		p1pll->min_post_div = 1;
		p1pll->max_post_div = 16;
		p1pll->min_frac_feedback_div = 0;
		p1pll->max_frac_feedback_div = 0;
		p2pll->min_post_div = 1;
		p2pll->max_post_div = 12;
		p2pll->min_frac_feedback_div = 0;
		p2pll->max_frac_feedback_div = 0;
	}

	p1pll->min_ref_div = 2;
	p1pll->max_ref_div = 0x3ff;
	p1pll->min_feedback_div = 4;
	p1pll->max_feedback_div = 0x7ff;
	p1pll->best_vco = 0;

	p2pll->min_ref_div = 2;
	p2pll->max_ref_div = 0x3ff;
	p2pll->min_feedback_div = 4;
	p2pll->max_feedback_div = 0x7ff;
	p2pll->best_vco = 0;

	/* system clock */
	spll->min_post_div = 1;
	spll->max_post_div = 1;
	spll->min_ref_div = 2;
	spll->max_ref_div = 0xff;
	spll->min_feedback_div = 4;
	spll->max_feedback_div = 0xff;
	spll->best_vco = 0;

	/* memory clock */
	mpll->min_post_div = 1;
	mpll->max_post_div = 1;
	mpll->min_ref_div = 2;
	mpll->max_ref_div = 0xff;
	mpll->min_feedback_div = 4;
	mpll->max_feedback_div = 0xff;
	mpll->best_vco = 0;

}

/* 10 khz */
static uint32_t calc_eng_mem_clock(struct radeon_device *rdev,
				   uint32_t req_clock,
				   int *fb_div, int *post_div)
{
	struct radeon_pll *spll = &rdev->clock.spll;
	int ref_div = spll->reference_div;

	if (!ref_div)
		ref_div =
		    RREG32_PLL(RADEON_M_SPLL_REF_FB_DIV) &
		    RADEON_M_SPLL_REF_DIV_MASK;

	if (req_clock < 15000) {
		*post_div = 8;
		req_clock *= 8;
	} else if (req_clock < 30000) {
		*post_div = 4;
		req_clock *= 4;
	} else if (req_clock < 60000) {
		*post_div = 2;
		req_clock *= 2;
	} else
		*post_div = 1;

	req_clock *= ref_div;
	req_clock += spll->reference_freq;
	req_clock /= (2 * spll->reference_freq);

	*fb_div = req_clock & 0xff;

	req_clock = (req_clock & 0xffff) << 1;
	req_clock *= spll->reference_freq;
	req_clock /= ref_div;
	req_clock /= *post_div;

	return req_clock;
}

/* 10 khz */
void radeon_legacy_set_engine_clock(struct radeon_device *rdev,
				    uint32_t eng_clock)
{
	uint32_t tmp;
	int fb_div, post_div;

	/* XXX: wait for idle */

	eng_clock = calc_eng_mem_clock(rdev, eng_clock, &fb_div, &post_div);

	tmp = RREG32_PLL(RADEON_CLK_PIN_CNTL);
	tmp &= ~RADEON_DONT_USE_XTALIN;
	WREG32_PLL(RADEON_CLK_PIN_CNTL, tmp);

	tmp = RREG32_PLL(RADEON_SCLK_CNTL);
	tmp &= ~RADEON_SCLK_SRC_SEL_MASK;
	WREG32_PLL(RADEON_SCLK_CNTL, tmp);

	udelay(10);

	tmp = RREG32_PLL(RADEON_SPLL_CNTL);
	tmp |= RADEON_SPLL_SLEEP;
	WREG32_PLL(RADEON_SPLL_CNTL, tmp);

	udelay(2);

	tmp = RREG32_PLL(RADEON_SPLL_CNTL);
	tmp |= RADEON_SPLL_RESET;
	WREG32_PLL(RADEON_SPLL_CNTL, tmp);

	udelay(200);

	tmp = RREG32_PLL(RADEON_M_SPLL_REF_FB_DIV);
	tmp &= ~(RADEON_SPLL_FB_DIV_MASK << RADEON_SPLL_FB_DIV_SHIFT);
	tmp |= (fb_div & RADEON_SPLL_FB_DIV_MASK) << RADEON_SPLL_FB_DIV_SHIFT;
	WREG32_PLL(RADEON_M_SPLL_REF_FB_DIV, tmp);

	/* XXX: verify on different asics */
	tmp = RREG32_PLL(RADEON_SPLL_CNTL);
	tmp &= ~RADEON_SPLL_PVG_MASK;
	if ((eng_clock * post_div) >= 90000)
		tmp |= (0x7 << RADEON_SPLL_PVG_SHIFT);
	else
		tmp |= (0x4 << RADEON_SPLL_PVG_SHIFT);
	WREG32_PLL(RADEON_SPLL_CNTL, tmp);

	tmp = RREG32_PLL(RADEON_SPLL_CNTL);
	tmp &= ~RADEON_SPLL_SLEEP;
	WREG32_PLL(RADEON_SPLL_CNTL, tmp);

	udelay(2);

	tmp = RREG32_PLL(RADEON_SPLL_CNTL);
	tmp &= ~RADEON_SPLL_RESET;
	WREG32_PLL(RADEON_SPLL_CNTL, tmp);

	udelay(200);

	tmp = RREG32_PLL(RADEON_SCLK_CNTL);
	tmp &= ~RADEON_SCLK_SRC_SEL_MASK;
	switch (post_div) {
	case 1:
	default:
		tmp |= 1;
		break;
	case 2:
		tmp |= 2;
		break;
	case 4:
		tmp |= 3;
		break;
	case 8:
		tmp |= 4;
		break;
	}
	WREG32_PLL(RADEON_SCLK_CNTL, tmp);

	udelay(20);

	tmp = RREG32_PLL(RADEON_CLK_PIN_CNTL);
	tmp |= RADEON_DONT_USE_XTALIN;
	WREG32_PLL(RADEON_CLK_PIN_CNTL, tmp);

	udelay(10);
}

void radeon_legacy_set_clock_gating(struct radeon_device *rdev, int enable)
{
	uint32_t tmp;

	if (enable) {
		if (rdev->flags & RADEON_SINGLE_CRTC) {
			tmp = RREG32_PLL(RADEON_SCLK_CNTL);
			if ((RREG32(RADEON_CONFIG_CNTL) &
			     RADEON_CFG_ATI_REV_ID_MASK) >
			    RADEON_CFG_ATI_REV_A13) {
				tmp &=
				    ~(RADEON_SCLK_FORCE_CP |
				      RADEON_SCLK_FORCE_RB);
			}
			tmp &=
			    ~(RADEON_SCLK_FORCE_HDP | RADEON_SCLK_FORCE_DISP1 |
			      RADEON_SCLK_FORCE_TOP | RADEON_SCLK_FORCE_SE |
			      RADEON_SCLK_FORCE_IDCT | RADEON_SCLK_FORCE_RE |
			      RADEON_SCLK_FORCE_PB | RADEON_SCLK_FORCE_TAM |
			      RADEON_SCLK_FORCE_TDM);
			WREG32_PLL(RADEON_SCLK_CNTL, tmp);
		} else if (ASIC_IS_R300(rdev)) {
			if ((rdev->family == CHIP_RS400) ||
			    (rdev->family == CHIP_RS480)) {
				tmp = RREG32_PLL(RADEON_SCLK_CNTL);
				tmp &=
				    ~(RADEON_SCLK_FORCE_DISP2 |
				      RADEON_SCLK_FORCE_CP |
				      RADEON_SCLK_FORCE_HDP |
				      RADEON_SCLK_FORCE_DISP1 |
				      RADEON_SCLK_FORCE_TOP |
				      RADEON_SCLK_FORCE_E2 | R300_SCLK_FORCE_VAP
				      | RADEON_SCLK_FORCE_IDCT |
				      RADEON_SCLK_FORCE_VIP | R300_SCLK_FORCE_SR
				      | R300_SCLK_FORCE_PX | R300_SCLK_FORCE_TX
				      | R300_SCLK_FORCE_US |
				      RADEON_SCLK_FORCE_TV_SCLK |
				      R300_SCLK_FORCE_SU |
				      RADEON_SCLK_FORCE_OV0);
				tmp |= RADEON_DYN_STOP_LAT_MASK;
				tmp |=
				    RADEON_SCLK_FORCE_TOP |
				    RADEON_SCLK_FORCE_VIP;
				WREG32_PLL(RADEON_SCLK_CNTL, tmp);

				tmp = RREG32_PLL(RADEON_SCLK_MORE_CNTL);
				tmp &= ~RADEON_SCLK_MORE_FORCEON;
				tmp |= RADEON_SCLK_MORE_MAX_DYN_STOP_LAT;
				WREG32_PLL(RADEON_SCLK_MORE_CNTL, tmp);

				tmp = RREG32_PLL(RADEON_VCLK_ECP_CNTL);
				tmp |= (RADEON_PIXCLK_ALWAYS_ONb |
					RADEON_PIXCLK_DAC_ALWAYS_ONb);
				WREG32_PLL(RADEON_VCLK_ECP_CNTL, tmp);

				tmp = RREG32_PLL(RADEON_PIXCLKS_CNTL);
				tmp |= (RADEON_PIX2CLK_ALWAYS_ONb |
					RADEON_PIX2CLK_DAC_ALWAYS_ONb |
					RADEON_DISP_TVOUT_PIXCLK_TV_ALWAYS_ONb |
					R300_DVOCLK_ALWAYS_ONb |
					RADEON_PIXCLK_BLEND_ALWAYS_ONb |
					RADEON_PIXCLK_GV_ALWAYS_ONb |
					R300_PIXCLK_DVO_ALWAYS_ONb |
					RADEON_PIXCLK_LVDS_ALWAYS_ONb |
					RADEON_PIXCLK_TMDS_ALWAYS_ONb |
					R300_PIXCLK_TRANS_ALWAYS_ONb |
					R300_PIXCLK_TVO_ALWAYS_ONb |
					R300_P2G2CLK_ALWAYS_ONb |
					R300_P2G2CLK_DAC_ALWAYS_ONb);
				WREG32_PLL(RADEON_PIXCLKS_CNTL, tmp);
			} else if (rdev->family >= CHIP_RV350) {
				tmp = RREG32_PLL(R300_SCLK_CNTL2);
				tmp &= ~(R300_SCLK_FORCE_TCL |
					 R300_SCLK_FORCE_GA |
					 R300_SCLK_FORCE_CBA);
				tmp |= (R300_SCLK_TCL_MAX_DYN_STOP_LAT |
					R300_SCLK_GA_MAX_DYN_STOP_LAT |
					R300_SCLK_CBA_MAX_DYN_STOP_LAT);
				WREG32_PLL(R300_SCLK_CNTL2, tmp);

				tmp = RREG32_PLL(RADEON_SCLK_CNTL);
				tmp &=
				    ~(RADEON_SCLK_FORCE_DISP2 |
				      RADEON_SCLK_FORCE_CP |
				      RADEON_SCLK_FORCE_HDP |
				      RADEON_SCLK_FORCE_DISP1 |
				      RADEON_SCLK_FORCE_TOP |
				      RADEON_SCLK_FORCE_E2 | R300_SCLK_FORCE_VAP
				      | RADEON_SCLK_FORCE_IDCT |
				      RADEON_SCLK_FORCE_VIP | R300_SCLK_FORCE_SR
				      | R300_SCLK_FORCE_PX | R300_SCLK_FORCE_TX
				      | R300_SCLK_FORCE_US |
				      RADEON_SCLK_FORCE_TV_SCLK |
				      R300_SCLK_FORCE_SU |
				      RADEON_SCLK_FORCE_OV0);
				tmp |= RADEON_DYN_STOP_LAT_MASK;
				WREG32_PLL(RADEON_SCLK_CNTL, tmp);

				tmp = RREG32_PLL(RADEON_SCLK_MORE_CNTL);
				tmp &= ~RADEON_SCLK_MORE_FORCEON;
				tmp |= RADEON_SCLK_MORE_MAX_DYN_STOP_LAT;
				WREG32_PLL(RADEON_SCLK_MORE_CNTL, tmp);

				tmp = RREG32_PLL(RADEON_VCLK_ECP_CNTL);
				tmp |= (RADEON_PIXCLK_ALWAYS_ONb |
					RADEON_PIXCLK_DAC_ALWAYS_ONb);
				WREG32_PLL(RADEON_VCLK_ECP_CNTL, tmp);

				tmp = RREG32_PLL(RADEON_PIXCLKS_CNTL);
				tmp |= (RADEON_PIX2CLK_ALWAYS_ONb |
					RADEON_PIX2CLK_DAC_ALWAYS_ONb |
					RADEON_DISP_TVOUT_PIXCLK_TV_ALWAYS_ONb |
					R300_DVOCLK_ALWAYS_ONb |
					RADEON_PIXCLK_BLEND_ALWAYS_ONb |
					RADEON_PIXCLK_GV_ALWAYS_ONb |
					R300_PIXCLK_DVO_ALWAYS_ONb |
					RADEON_PIXCLK_LVDS_ALWAYS_ONb |
					RADEON_PIXCLK_TMDS_ALWAYS_ONb |
					R300_PIXCLK_TRANS_ALWAYS_ONb |
					R300_PIXCLK_TVO_ALWAYS_ONb |
					R300_P2G2CLK_ALWAYS_ONb |
					R300_P2G2CLK_DAC_ALWAYS_ONb);
				WREG32_PLL(RADEON_PIXCLKS_CNTL, tmp);

				tmp = RREG32_PLL(RADEON_MCLK_MISC);
				tmp |= (RADEON_MC_MCLK_DYN_ENABLE |
					RADEON_IO_MCLK_DYN_ENABLE);
				WREG32_PLL(RADEON_MCLK_MISC, tmp);

				tmp = RREG32_PLL(RADEON_MCLK_CNTL);
				tmp |= (RADEON_FORCEON_MCLKA |
					RADEON_FORCEON_MCLKB);

				tmp &= ~(RADEON_FORCEON_YCLKA |
					 RADEON_FORCEON_YCLKB |
					 RADEON_FORCEON_MC);

				/* Some releases of vbios have set DISABLE_MC_MCLKA
				   and DISABLE_MC_MCLKB bits in the vbios table.  Setting these
				   bits will cause H/W hang when reading video memory with dynamic clocking
				   enabled. */
				if ((tmp & R300_DISABLE_MC_MCLKA) &&
				    (tmp & R300_DISABLE_MC_MCLKB)) {
					/* If both bits are set, then check the active channels */
					tmp = RREG32_PLL(RADEON_MCLK_CNTL);
					if (rdev->mc.vram_width == 64) {
						if (RREG32(RADEON_MEM_CNTL) &
						    R300_MEM_USE_CD_CH_ONLY)
							tmp &=
							    ~R300_DISABLE_MC_MCLKB;
						else
							tmp &=
							    ~R300_DISABLE_MC_MCLKA;
					} else {
						tmp &= ~(R300_DISABLE_MC_MCLKA |
							 R300_DISABLE_MC_MCLKB);
					}
				}

				WREG32_PLL(RADEON_MCLK_CNTL, tmp);
			} else {
				tmp = RREG32_PLL(RADEON_SCLK_CNTL);
				tmp &= ~(R300_SCLK_FORCE_VAP);
				tmp |= RADEON_SCLK_FORCE_CP;
				WREG32_PLL(RADEON_SCLK_CNTL, tmp);
				udelay(15000);

				tmp = RREG32_PLL(R300_SCLK_CNTL2);
				tmp &= ~(R300_SCLK_FORCE_TCL |
					 R300_SCLK_FORCE_GA |
					 R300_SCLK_FORCE_CBA);
				WREG32_PLL(R300_SCLK_CNTL2, tmp);
			}
		} else {
			tmp = RREG32_PLL(RADEON_CLK_PWRMGT_CNTL);

			tmp &= ~(RADEON_ACTIVE_HILO_LAT_MASK |
				 RADEON_DISP_DYN_STOP_LAT_MASK |
				 RADEON_DYN_STOP_MODE_MASK);

			tmp |= (RADEON_ENGIN_DYNCLK_MODE |
				(0x01 << RADEON_ACTIVE_HILO_LAT_SHIFT));
			WREG32_PLL(RADEON_CLK_PWRMGT_CNTL, tmp);
			udelay(15000);

			tmp = RREG32_PLL(RADEON_CLK_PIN_CNTL);
			tmp |= RADEON_SCLK_DYN_START_CNTL;
			WREG32_PLL(RADEON_CLK_PIN_CNTL, tmp);
			udelay(15000);

			/* When DRI is enabled, setting DYN_STOP_LAT to zero can cause some R200
			   to lockup randomly, leave them as set by BIOS.
			 */
			tmp = RREG32_PLL(RADEON_SCLK_CNTL);
			/*tmp &= RADEON_SCLK_SRC_SEL_MASK; */
			tmp &= ~RADEON_SCLK_FORCEON_MASK;

			/*RAGE_6::A11 A12 A12N1 A13, RV250::A11 A12, R300 */
			if (((rdev->family == CHIP_RV250) &&
			     ((RREG32(RADEON_CONFIG_CNTL) &
			       RADEON_CFG_ATI_REV_ID_MASK) <
			      RADEON_CFG_ATI_REV_A13))
			    || ((rdev->family == CHIP_RV100)
				&&
				((RREG32(RADEON_CONFIG_CNTL) &
				  RADEON_CFG_ATI_REV_ID_MASK) <=
				 RADEON_CFG_ATI_REV_A13))) {
				tmp |= RADEON_SCLK_FORCE_CP;
				tmp |= RADEON_SCLK_FORCE_VIP;
			}

			WREG32_PLL(RADEON_SCLK_CNTL, tmp);

			if ((rdev->family == CHIP_RV200) ||
			    (rdev->family == CHIP_RV250) ||
			    (rdev->family == CHIP_RV280)) {
				tmp = RREG32_PLL(RADEON_SCLK_MORE_CNTL);
				tmp &= ~RADEON_SCLK_MORE_FORCEON;

				/* RV200::A11 A12 RV250::A11 A12 */
				if (((rdev->family == CHIP_RV200) ||
				     (rdev->family == CHIP_RV250)) &&
				    ((RREG32(RADEON_CONFIG_CNTL) &
				      RADEON_CFG_ATI_REV_ID_MASK) <
				     RADEON_CFG_ATI_REV_A13)) {
					tmp |= RADEON_SCLK_MORE_FORCEON;
				}
				WREG32_PLL(RADEON_SCLK_MORE_CNTL, tmp);
				udelay(15000);
			}

			/* RV200::A11 A12, RV250::A11 A12 */
			if (((rdev->family == CHIP_RV200) ||
			     (rdev->family == CHIP_RV250)) &&
			    ((RREG32(RADEON_CONFIG_CNTL) &
			      RADEON_CFG_ATI_REV_ID_MASK) <
			     RADEON_CFG_ATI_REV_A13)) {
				tmp = RREG32_PLL(RADEON_PLL_PWRMGT_CNTL);
				tmp |= RADEON_TCL_BYPASS_DISABLE;
				WREG32_PLL(RADEON_PLL_PWRMGT_CNTL, tmp);
			}
			udelay(15000);

			/*enable dynamic mode for display clocks (PIXCLK and PIX2CLK) */
			tmp = RREG32_PLL(RADEON_PIXCLKS_CNTL);
			tmp |= (RADEON_PIX2CLK_ALWAYS_ONb |
				RADEON_PIX2CLK_DAC_ALWAYS_ONb |
				RADEON_PIXCLK_BLEND_ALWAYS_ONb |
				RADEON_PIXCLK_GV_ALWAYS_ONb |
				RADEON_PIXCLK_DIG_TMDS_ALWAYS_ONb |
				RADEON_PIXCLK_LVDS_ALWAYS_ONb |
				RADEON_PIXCLK_TMDS_ALWAYS_ONb);

			WREG32_PLL(RADEON_PIXCLKS_CNTL, tmp);
			udelay(15000);

			tmp = RREG32_PLL(RADEON_VCLK_ECP_CNTL);
			tmp |= (RADEON_PIXCLK_ALWAYS_ONb |
				RADEON_PIXCLK_DAC_ALWAYS_ONb);

			WREG32_PLL(RADEON_VCLK_ECP_CNTL, tmp);
			udelay(15000);
		}
	} else {
		/* Turn everything OFF (ForceON to everything) */
		if (rdev->flags & RADEON_SINGLE_CRTC) {
			tmp = RREG32_PLL(RADEON_SCLK_CNTL);
			tmp |= (RADEON_SCLK_FORCE_CP | RADEON_SCLK_FORCE_HDP |
				RADEON_SCLK_FORCE_DISP1 | RADEON_SCLK_FORCE_TOP
				| RADEON_SCLK_FORCE_E2 | RADEON_SCLK_FORCE_SE |
				RADEON_SCLK_FORCE_IDCT | RADEON_SCLK_FORCE_VIP |
				RADEON_SCLK_FORCE_RE | RADEON_SCLK_FORCE_PB |
				RADEON_SCLK_FORCE_TAM | RADEON_SCLK_FORCE_TDM |
				RADEON_SCLK_FORCE_RB);
			WREG32_PLL(RADEON_SCLK_CNTL, tmp);
		} else if ((rdev->family == CHIP_RS400) ||
			   (rdev->family == CHIP_RS480)) {
			tmp = RREG32_PLL(RADEON_SCLK_CNTL);
			tmp |= (RADEON_SCLK_FORCE_DISP2 | RADEON_SCLK_FORCE_CP |
				RADEON_SCLK_FORCE_HDP | RADEON_SCLK_FORCE_DISP1
				| RADEON_SCLK_FORCE_TOP | RADEON_SCLK_FORCE_E2 |
				R300_SCLK_FORCE_VAP | RADEON_SCLK_FORCE_IDCT |
				RADEON_SCLK_FORCE_VIP | R300_SCLK_FORCE_SR |
				R300_SCLK_FORCE_PX | R300_SCLK_FORCE_TX |
				R300_SCLK_FORCE_US | RADEON_SCLK_FORCE_TV_SCLK |
				R300_SCLK_FORCE_SU | RADEON_SCLK_FORCE_OV0);
			WREG32_PLL(RADEON_SCLK_CNTL, tmp);

			tmp = RREG32_PLL(RADEON_SCLK_MORE_CNTL);
			tmp |= RADEON_SCLK_MORE_FORCEON;
			WREG32_PLL(RADEON_SCLK_MORE_CNTL, tmp);

			tmp = RREG32_PLL(RADEON_VCLK_ECP_CNTL);
			tmp &= ~(RADEON_PIXCLK_ALWAYS_ONb |
				 RADEON_PIXCLK_DAC_ALWAYS_ONb |
				 R300_DISP_DAC_PIXCLK_DAC_BLANK_OFF);
			WREG32_PLL(RADEON_VCLK_ECP_CNTL, tmp);

			tmp = RREG32_PLL(RADEON_PIXCLKS_CNTL);
			tmp &= ~(RADEON_PIX2CLK_ALWAYS_ONb |
				 RADEON_PIX2CLK_DAC_ALWAYS_ONb |
				 RADEON_DISP_TVOUT_PIXCLK_TV_ALWAYS_ONb |
				 R300_DVOCLK_ALWAYS_ONb |
				 RADEON_PIXCLK_BLEND_ALWAYS_ONb |
				 RADEON_PIXCLK_GV_ALWAYS_ONb |
				 R300_PIXCLK_DVO_ALWAYS_ONb |
				 RADEON_PIXCLK_LVDS_ALWAYS_ONb |
				 RADEON_PIXCLK_TMDS_ALWAYS_ONb |
				 R300_PIXCLK_TRANS_ALWAYS_ONb |
				 R300_PIXCLK_TVO_ALWAYS_ONb |
				 R300_P2G2CLK_ALWAYS_ONb |
				 R300_P2G2CLK_DAC_ALWAYS_ONb |
				 R300_DISP_DAC_PIXCLK_DAC2_BLANK_OFF);
			WREG32_PLL(RADEON_PIXCLKS_CNTL, tmp);
		} else if (rdev->family >= CHIP_RV350) {
			/* for RV350/M10, no delays are required. */
			tmp = RREG32_PLL(R300_SCLK_CNTL2);
			tmp |= (R300_SCLK_FORCE_TCL |
				R300_SCLK_FORCE_GA | R300_SCLK_FORCE_CBA);
			WREG32_PLL(R300_SCLK_CNTL2, tmp);

			tmp = RREG32_PLL(RADEON_SCLK_CNTL);
			tmp |= (RADEON_SCLK_FORCE_DISP2 | RADEON_SCLK_FORCE_CP |
				RADEON_SCLK_FORCE_HDP | RADEON_SCLK_FORCE_DISP1
				| RADEON_SCLK_FORCE_TOP | RADEON_SCLK_FORCE_E2 |
				R300_SCLK_FORCE_VAP | RADEON_SCLK_FORCE_IDCT |
				RADEON_SCLK_FORCE_VIP | R300_SCLK_FORCE_SR |
				R300_SCLK_FORCE_PX | R300_SCLK_FORCE_TX |
				R300_SCLK_FORCE_US | RADEON_SCLK_FORCE_TV_SCLK |
				R300_SCLK_FORCE_SU | RADEON_SCLK_FORCE_OV0);
			WREG32_PLL(RADEON_SCLK_CNTL, tmp);

			tmp = RREG32_PLL(RADEON_SCLK_MORE_CNTL);
			tmp |= RADEON_SCLK_MORE_FORCEON;
			WREG32_PLL(RADEON_SCLK_MORE_CNTL, tmp);

			tmp = RREG32_PLL(RADEON_MCLK_CNTL);
			tmp |= (RADEON_FORCEON_MCLKA |
				RADEON_FORCEON_MCLKB |
				RADEON_FORCEON_YCLKA |
				RADEON_FORCEON_YCLKB | RADEON_FORCEON_MC);
			WREG32_PLL(RADEON_MCLK_CNTL, tmp);

			tmp = RREG32_PLL(RADEON_VCLK_ECP_CNTL);
			tmp &= ~(RADEON_PIXCLK_ALWAYS_ONb |
				 RADEON_PIXCLK_DAC_ALWAYS_ONb |
				 R300_DISP_DAC_PIXCLK_DAC_BLANK_OFF);
			WREG32_PLL(RADEON_VCLK_ECP_CNTL, tmp);

			tmp = RREG32_PLL(RADEON_PIXCLKS_CNTL);
			tmp &= ~(RADEON_PIX2CLK_ALWAYS_ONb |
				 RADEON_PIX2CLK_DAC_ALWAYS_ONb |
				 RADEON_DISP_TVOUT_PIXCLK_TV_ALWAYS_ONb |
				 R300_DVOCLK_ALWAYS_ONb |
				 RADEON_PIXCLK_BLEND_ALWAYS_ONb |
				 RADEON_PIXCLK_GV_ALWAYS_ONb |
				 R300_PIXCLK_DVO_ALWAYS_ONb |
				 RADEON_PIXCLK_LVDS_ALWAYS_ONb |
				 RADEON_PIXCLK_TMDS_ALWAYS_ONb |
				 R300_PIXCLK_TRANS_ALWAYS_ONb |
				 R300_PIXCLK_TVO_ALWAYS_ONb |
				 R300_P2G2CLK_ALWAYS_ONb |
				 R300_P2G2CLK_DAC_ALWAYS_ONb |
				 R300_DISP_DAC_PIXCLK_DAC2_BLANK_OFF);
			WREG32_PLL(RADEON_PIXCLKS_CNTL, tmp);
		} else {
			tmp = RREG32_PLL(RADEON_SCLK_CNTL);
			tmp |= (RADEON_SCLK_FORCE_CP | RADEON_SCLK_FORCE_E2);
			tmp |= RADEON_SCLK_FORCE_SE;

			if (rdev->flags & RADEON_SINGLE_CRTC) {
				tmp |= (RADEON_SCLK_FORCE_RB |
					RADEON_SCLK_FORCE_TDM |
					RADEON_SCLK_FORCE_TAM |
					RADEON_SCLK_FORCE_PB |
					RADEON_SCLK_FORCE_RE |
					RADEON_SCLK_FORCE_VIP |
					RADEON_SCLK_FORCE_IDCT |
					RADEON_SCLK_FORCE_TOP |
					RADEON_SCLK_FORCE_DISP1 |
					RADEON_SCLK_FORCE_DISP2 |
					RADEON_SCLK_FORCE_HDP);
			} else if ((rdev->family == CHIP_R300) ||
				   (rdev->family == CHIP_R350)) {
				tmp |= (RADEON_SCLK_FORCE_HDP |
					RADEON_SCLK_FORCE_DISP1 |
					RADEON_SCLK_FORCE_DISP2 |
					RADEON_SCLK_FORCE_TOP |
					RADEON_SCLK_FORCE_IDCT |
					RADEON_SCLK_FORCE_VIP);
			}
			WREG32_PLL(RADEON_SCLK_CNTL, tmp);

			udelay(16000);

			if ((rdev->family == CHIP_R300) ||
			    (rdev->family == CHIP_R350)) {
				tmp = RREG32_PLL(R300_SCLK_CNTL2);
				tmp |= (R300_SCLK_FORCE_TCL |
					R300_SCLK_FORCE_GA |
					R300_SCLK_FORCE_CBA);
				WREG32_PLL(R300_SCLK_CNTL2, tmp);
				udelay(16000);
			}

			if (rdev->flags & RADEON_IS_IGP) {
				tmp = RREG32_PLL(RADEON_MCLK_CNTL);
				tmp &= ~(RADEON_FORCEON_MCLKA |
					 RADEON_FORCEON_YCLKA);
				WREG32_PLL(RADEON_MCLK_CNTL, tmp);
				udelay(16000);
			}

			if ((rdev->family == CHIP_RV200) ||
			    (rdev->family == CHIP_RV250) ||
			    (rdev->family == CHIP_RV280)) {
				tmp = RREG32_PLL(RADEON_SCLK_MORE_CNTL);
				tmp |= RADEON_SCLK_MORE_FORCEON;
				WREG32_PLL(RADEON_SCLK_MORE_CNTL, tmp);
				udelay(16000);
			}

			tmp = RREG32_PLL(RADEON_PIXCLKS_CNTL);
			tmp &= ~(RADEON_PIX2CLK_ALWAYS_ONb |
				 RADEON_PIX2CLK_DAC_ALWAYS_ONb |
				 RADEON_PIXCLK_BLEND_ALWAYS_ONb |
				 RADEON_PIXCLK_GV_ALWAYS_ONb |
				 RADEON_PIXCLK_DIG_TMDS_ALWAYS_ONb |
				 RADEON_PIXCLK_LVDS_ALWAYS_ONb |
				 RADEON_PIXCLK_TMDS_ALWAYS_ONb);

			WREG32_PLL(RADEON_PIXCLKS_CNTL, tmp);
			udelay(16000);

			tmp = RREG32_PLL(RADEON_VCLK_ECP_CNTL);
			tmp &= ~(RADEON_PIXCLK_ALWAYS_ONb |
				 RADEON_PIXCLK_DAC_ALWAYS_ONb);
			WREG32_PLL(RADEON_VCLK_ECP_CNTL, tmp);
		}
	}
}

static void radeon_apply_clock_quirks(struct radeon_device *rdev)
{
	uint32_t tmp;

	/* XXX make sure engine is idle */

	if (rdev->family < CHIP_RS600) {
		tmp = RREG32_PLL(RADEON_SCLK_CNTL);
		if (ASIC_IS_R300(rdev) || ASIC_IS_RV100(rdev))
			tmp |= RADEON_SCLK_FORCE_CP | RADEON_SCLK_FORCE_VIP;
		if ((rdev->family == CHIP_RV250)
		    || (rdev->family == CHIP_RV280))
			tmp |=
			    RADEON_SCLK_FORCE_DISP1 | RADEON_SCLK_FORCE_DISP2;
		if ((rdev->family == CHIP_RV350)
		    || (rdev->family == CHIP_RV380))
			tmp |= R300_SCLK_FORCE_VAP;
		if (rdev->family == CHIP_R420)
			tmp |= R300_SCLK_FORCE_PX | R300_SCLK_FORCE_TX;
		WREG32_PLL(RADEON_SCLK_CNTL, tmp);
	} else if (rdev->family < CHIP_R600) {
		tmp = RREG32_PLL(AVIVO_CP_DYN_CNTL);
		tmp |= AVIVO_CP_FORCEON;
		WREG32_PLL(AVIVO_CP_DYN_CNTL, tmp);

		tmp = RREG32_PLL(AVIVO_E2_DYN_CNTL);
		tmp |= AVIVO_E2_FORCEON;
		WREG32_PLL(AVIVO_E2_DYN_CNTL, tmp);

		tmp = RREG32_PLL(AVIVO_IDCT_DYN_CNTL);
		tmp |= AVIVO_IDCT_FORCEON;
		WREG32_PLL(AVIVO_IDCT_DYN_CNTL, tmp);
	}
}

int radeon_static_clocks_init(struct drm_device *dev)
{
	struct radeon_device *rdev = dev->dev_private;

	/* XXX make sure engine is idle */

	if (radeon_dynclks != -1) {
		if (radeon_dynclks) {
			if (rdev->asic->set_clock_gating)
				radeon_set_clock_gating(rdev, 1);
		}
	}
	radeon_apply_clock_quirks(rdev);
	return 0;
}