| /* Copyright 2023 The ChromiumOS Authors | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 | #include <zephyr/devicetree.h> | 
 | #include <zephyr/sys/util.h> | 
 |  | 
 | /* Be warned: the interface here is poorly understood.  I did the best | 
 |  * I could to transcribe it (with a little clarification and | 
 |  * optimization) from the SOF mt8195 source, but without docs this | 
 |  * needs to be treated with great care. | 
 |  * | 
 |  * Notes: | 
 |  * * power-on default is 26Mhz, confirmed with a hacked SOF that | 
 |  *   loads but stubs out the clk code. | 
 |  * * The original driver has a 13Mhz mode too, but it doesn't work (it | 
 |  *   hits all the same code and data paths as 26MHz and acts as a | 
 |  *   duplicate. | 
 |  * * The magic numbers in the pll_con2 field are from the original | 
 |  *   source.  No docs on the PLL register interface are provided. | 
 |  */ | 
 |  | 
 | struct mtk_pll_control { | 
 | 	uint32_t con0; | 
 | 	uint32_t con1; | 
 | 	uint32_t con2; | 
 | 	uint32_t con3; | 
 | 	uint32_t con4; | 
 | }; | 
 |  | 
 | #define MTK_PLL_CTRL (*(volatile struct mtk_pll_control *)		\ | 
 | 		      DT_PROP(DT_NODELABEL(cpuclk), pll_ctrl_reg)) | 
 |  | 
 | #define MTK_PLL_CON0_BASE_EN BIT(0) | 
 | #define MTK_PLL_CON0_EN      BIT(9) | 
 | #define MTK_PLL_CON4_ISO_EN  BIT(1) | 
 | #define MTK_PLL_CON4_PWR_ON  BIT(0) | 
 |  | 
 | struct mtk_clk_gen { | 
 | 	uint32_t mode; | 
 | 	uint32_t update[4]; | 
 | 	uint32_t _unused[3]; | 
 | 	struct { | 
 | 		uint32_t cur; | 
 | 		uint32_t set; | 
 | 		uint32_t clr; | 
 | 	} clk_cfg[29]; | 
 | }; | 
 |  | 
 | #define MTK_CLK_GEN (*(volatile struct mtk_clk_gen *) \ | 
 | 		     DT_REG_ADDR(DT_NODELABEL(cpuclk))) | 
 |  | 
 | #define MTK_CLK22_SEL_PLL 8 | 
 | #define MTK_CLK22_SEL_26M 0 | 
 |  | 
 | #define MTK_CLK28_SEL_PLL 7 | 
 | #define MTK_CLK28_SEL_26M 0 | 
 |  | 
 | #define MTK_CK_CG (*(volatile uint32_t *)			\ | 
 | 		   DT_PROP(DT_NODELABEL(cpuclk), cg_reg)) | 
 |  | 
 | #define MTK_CK_CG_SW 1 | 
 |  | 
 | const struct { uint16_t mhz; bool pll; uint32_t pll_con2; } freqs[] = { | 
 | 	{  26, false, 0 }, | 
 | 	{ 370,  true, 0x831c7628 }, | 
 | 	{ 540,  true, 0x8214c4ed }, | 
 | 	{ 720,  true, 0x821bb13c }, | 
 | }; | 
 |  | 
 | static int cur_idx; | 
 |  | 
 | /* Can't use CPU-counted loops when changing CPU speed, and don't have | 
 |  * an OS timer driver yet.  Use the 13 MHz timer hardware directly. | 
 |  * (The ostimer is always running AFAICT, there's not even an | 
 |  * interface for a disable bit defined) | 
 |  */ | 
 | #define TIMER (((volatile uint32_t *)DT_REG_ADDR(DT_NODELABEL(ostimer64)))[3]) | 
 | static inline void delay_us(int us) | 
 | { | 
 | 	uint32_t t0 = TIMER; | 
 |  | 
 | 	while (TIMER - t0 < (us * 13)) { | 
 | 	} | 
 | } | 
 |  | 
 | static void set_pll_power(bool on) | 
 | { | 
 | 	if (on) { | 
 | 		MTK_CK_CG &= ~MTK_CK_CG_SW; | 
 | 		MTK_PLL_CTRL.con4 |= MTK_PLL_CON4_PWR_ON; | 
 | 		delay_us(1); | 
 | 		MTK_PLL_CTRL.con4 &= ~MTK_PLL_CON4_ISO_EN; | 
 | 		delay_us(1); | 
 | 		MTK_PLL_CTRL.con0 |= MTK_PLL_CON0_EN; | 
 | 		delay_us(20); | 
 | 	} else { | 
 | 		MTK_PLL_CTRL.con0 &= ~MTK_PLL_CON0_EN; | 
 | 		delay_us(1); | 
 | 		MTK_PLL_CTRL.con4 |= MTK_PLL_CON4_ISO_EN; | 
 | 		delay_us(1); | 
 | 		MTK_PLL_CTRL.con4 &= ~MTK_PLL_CON4_PWR_ON; | 
 | 	} | 
 | } | 
 |  | 
 | /* Oddball utility.  There is a giant array of clocks (of which SOF | 
 |  * only touches two), each with "clear" and "set" registers which are | 
 |  * used to set 4-bit fields at a specific offset.  After that, a | 
 |  * particular bit in one of the "update" registers must be written, | 
 |  * presumably to latch the input. | 
 |  */ | 
 | static void setclk(int clk, int shift, int updreg, int ubit, int val) | 
 | { | 
 | 	MTK_CLK_GEN.clk_cfg[clk].clr = (0xf << shift); | 
 | 	if (val) { | 
 | 		MTK_CLK_GEN.clk_cfg[clk].set = (val << shift); | 
 | 	} | 
 | 	MTK_CLK_GEN.update[updreg] = BIT(ubit); | 
 | } | 
 |  | 
 | #define SETCLK22(val) setclk(22, 0, 2, 24, (val)) | 
 | #define SETCLK28(val) setclk(28, 16, 3, 18, (val)) | 
 |  | 
 | void mtk_adsp_set_cpu_freq(int mhz) | 
 | { | 
 | 	int idx; | 
 |  | 
 | 	for (idx = 0; idx < ARRAY_SIZE(freqs); idx++) { | 
 | 		if (freqs[idx].mhz == mhz) { | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (idx == cur_idx || freqs[idx].mhz != mhz) { | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	if (freqs[idx].pll) { | 
 | 		/* Switch to PLL from 26Mhz */ | 
 | 		set_pll_power(true); | 
 | 		SETCLK22(MTK_CLK22_SEL_PLL); | 
 | 		SETCLK28(MTK_CLK28_SEL_PLL); | 
 | 		MTK_PLL_CTRL.con2 = freqs[idx].pll_con2; | 
 | 	} else { | 
 | 		/* Switch to 26Mhz from PLL */ | 
 | 		SETCLK28(MTK_CLK28_SEL_26M); | 
 | 		SETCLK22(MTK_CLK22_SEL_26M); | 
 | 		set_pll_power(false); | 
 | 	} | 
 |  | 
 | 	cur_idx = idx; | 
 | } | 
 |  | 
 | /* The CPU clock is not affected (!) by device reset, so we don't know | 
 |  * the speed we're at (on MT8195, hardware powers up at 26 Mhz, but | 
 |  * production SOF firmware sets it to 720 at load time and leaves it | 
 |  * there).  Set the lowest, then the highest speed unconditionally to | 
 |  * force the transition. | 
 |  */ | 
 | void mtk_adsp_cpu_freq_init(void) | 
 | { | 
 | 	mtk_adsp_set_cpu_freq(freqs[0].mhz); | 
 | 	mtk_adsp_set_cpu_freq(freqs[ARRAY_SIZE(freqs) - 1].mhz); | 
 | } |