| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Copyright (c) 2019 MediaTek Inc. | 
 |  * Author: Pierre Lee <pierre.lee@mediatek.com> | 
 |  */ | 
 |  | 
 | #include <linux/delay.h> | 
 | #include <linux/iopoll.h> | 
 | #include "clk-fhctl.h" | 
 |  | 
 | #define PERCENT_TO_DDSLMT(dds, percent_m10) \ | 
 | 	((((dds) * (percent_m10)) >> 5) / 100) | 
 |  | 
 |  | 
 | #define POSDIV_LSB 24 | 
 | #define POSDIV_MASK 0x7 | 
 |  | 
 |  | 
 | static int mt_fh_ipi_init(void) | 
 | { | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static int __mt_fh_hw_hopping(struct clk_mt_fhctl *fh, | 
 | 				int pll_id, unsigned int new_dds, | 
 | 				int postdiv) | 
 | { | 
 | 	unsigned int dds_mask, con_temp; | 
 | 	unsigned int mon_dds = 0; | 
 | 	int ret; | 
 | 	struct clk_mt_fhctl_regs *fh_regs; | 
 | 	struct clk_mt_fhctl_pll_data *pll_data; | 
 |  | 
 | 	fh_regs = fh->fh_regs; | 
 | 	pll_data = fh->pll_data; | 
 | 	dds_mask = fh->pll_data->dds_mask; | 
 |  | 
 | 	if (new_dds > dds_mask) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* 1. sync ncpo to DDS of FHCTL */ | 
 | 	writel((readl(fh_regs->reg_con_pcw) & pll_data->dds_mask) | | 
 | 			FH_FHCTLX_PLL_TGL_ORG, fh_regs->reg_dds); | 
 |  | 
 | 	/* 2. enable DVFS and Hopping control */ | 
 |  | 
 | 	/* enable dvfs mode */ | 
 | 	fh_set_field(fh_regs->reg_cfg, FH_SFSTRX_EN, 1); | 
 | 	/* enable hopping */ | 
 | 	fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 1); | 
 |  | 
 | 	/* for slope setting. */ | 
 |  | 
 | 	writel(pll_data->slope0_value, fh_regs->reg_slope0); | 
 | 	/* SLOPE1 is for MEMPLL */ | 
 | 	writel(pll_data->slope1_value, fh_regs->reg_slope1); | 
 |  | 
 | 	/* 3. switch to hopping control */ | 
 | 	fh_set_field(fh_regs->reg_hp_en, (0x1U << pll_id), | 
 | 						REG_HP_EN_FHCTL_CTR); | 
 |  | 
 | 	/* 4. set DFS DDS */ | 
 | 	writel((new_dds) | (FH_FHCTLX_PLL_DVFS_TRI), fh_regs->reg_dvfs); | 
 |  | 
 | 	/* 4.1 ensure jump to target DDS */ | 
 | 	/* Wait 1000 us until DDS stable */ | 
 | 	ret = readl_poll_timeout_atomic(fh_regs->reg_mon, mon_dds, | 
 | 			(mon_dds&pll_data->dds_mask) == new_dds, 10, 1000); | 
 | 	if (ret) | 
 | 		pr_info("ERROR %s: target_dds=0x%x, mon_dds=0x%x", | 
 | 			__func__, new_dds, (mon_dds&pll_data->dds_mask)); | 
 |  | 
 | 	if (postdiv == -1) { | 
 | 		/* Don't change DIV for fhctl UT */ | 
 | 		con_temp = readl(fh_regs->reg_con_pcw) & (~dds_mask); | 
 | 		con_temp = (con_temp | | 
 | 				(readl(fh_regs->reg_mon) & dds_mask) | | 
 | 				FH_XXPLL_CON_PCWCHG); | 
 | 	} else { | 
 | 		con_temp = readl(fh_regs->reg_con_pcw) & (~dds_mask); | 
 | 		con_temp = con_temp & ~(POSDIV_MASK << POSDIV_LSB); | 
 | 		con_temp = (con_temp | | 
 | 				(readl(fh_regs->reg_mon) & dds_mask) | | 
 | 				(postdiv & POSDIV_MASK) << POSDIV_LSB | | 
 | 				FH_XXPLL_CON_PCWCHG); | 
 | 	} | 
 |  | 
 | 	/* 5. write back to ncpo */ | 
 | 	writel(con_temp, fh_regs->reg_con_pcw); | 
 |  | 
 | 	/* 6. switch to APMIXEDSYS control */ | 
 | 	fh_set_field(fh_regs->reg_hp_en, BIT(pll_id), | 
 | 				REG_HP_EN_APMIXEDSYS_CTR); | 
 |  | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static int clk_mt_fh_hw_pll_init(struct clk_mt_fhctl *fh) | 
 | { | 
 | 	struct clk_mt_fhctl_regs *fh_regs; | 
 | 	struct clk_mt_fhctl_pll_data *pll_data; | 
 | 	int pll_id; | 
 | 	unsigned int mask; | 
 |  | 
 | 	pr_debug("mt_fh_pll_init() start "); | 
 |  | 
 | 	pll_id = fh->pll_data->pll_id; | 
 | 	fh_regs = fh->fh_regs; | 
 | 	pll_data = fh->pll_data; | 
 |  | 
 | 	mask = 1 << pll_id; | 
 |  | 
 | 	if (fh_regs == NULL) { | 
 | 		pr_info("ERROR fh_reg (%d) is NULL", pll_id); | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	if (pll_data == NULL) { | 
 | 		pr_info("ERROR pll_data (%d) is NULL", pll_id); | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	fh_set_field(fh_regs->reg_clk_con, mask, 1); | 
 |  | 
 | 	/* Release software-reset to reset */ | 
 | 	fh_set_field(fh_regs->reg_rst_con, mask, 0); | 
 | 	fh_set_field(fh_regs->reg_rst_con, mask, 1); | 
 |  | 
 | 	writel(0x00000000, fh_regs->reg_cfg);	/* No SSC/FH enabled */ | 
 | 	writel(0x00000000, fh_regs->reg_updnlmt); /* clr all setting */ | 
 | 	writel(0x00000000, fh_regs->reg_dds);	/* clr all settings */ | 
 |  | 
 | 	/* Check default enable SSC */ | 
 | 	if (pll_data->pll_default_ssc_rate != 0) { | 
 | 		// Default Enable SSC to 0~-N%; | 
 | 		fh->hal_ops->pll_ssc_enable(fh, pll_data->pll_default_ssc_rate); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int clk_mt_fh_hw_pll_unpause(struct clk_mt_fhctl *fh) | 
 | { | 
 | 	int pll_id; | 
 | 	struct clk_mt_fhctl_regs *fh_regs; | 
 | 	unsigned long flags = 0; | 
 |  | 
 |  | 
 | 	pll_id = fh->pll_data->pll_id; | 
 | 	fh_regs = fh->fh_regs; | 
 |  | 
 | 	if (fh->pll_data->pll_type != FH_PLL_TYPE_CPU) { | 
 | 		pr_info("%s not support unpause.", fh->pll_data->pll_name); | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	pr_debug("%s fh_pll_id:%d", __func__, pll_id); | 
 |  | 
 | 	spin_lock_irqsave(fh->lock, flags); | 
 |  | 
 | 	/* unpause  */ | 
 | 	fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_CFG_PAUSE, 0); | 
 |  | 
 | 	spin_unlock_irqrestore(fh->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int clk_mt_fh_hw_pll_pause(struct clk_mt_fhctl *fh) | 
 | { | 
 | 	int pll_id; | 
 | 	struct clk_mt_fhctl_regs *fh_regs; | 
 | 	unsigned long flags = 0; | 
 |  | 
 | 	pll_id = fh->pll_data->pll_id; | 
 | 	fh_regs = fh->fh_regs; | 
 |  | 
 | 	if (fh->pll_data->pll_type != FH_PLL_TYPE_CPU) { | 
 | 		pr_info("%s not support pause.", fh->pll_data->pll_name); | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	pr_debug("%s fh_pll_id:%d", __func__, pll_id); | 
 |  | 
 | 	spin_lock_irqsave(fh->lock, flags); | 
 |  | 
 | 	/* pause  */ | 
 | 	fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_CFG_PAUSE, 1); | 
 |  | 
 | 	spin_unlock_irqrestore(fh->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int clk_mt_fh_hw_pll_ssc_disable(struct clk_mt_fhctl *fh) | 
 | { | 
 | 	int pll_id; | 
 | 	unsigned long flags = 0; | 
 | 	struct clk_mt_fhctl_regs *fh_regs; | 
 | 	struct clk_mt_fhctl_pll_data *pll_data; | 
 |  | 
 |  | 
 | 	pll_id = fh->pll_data->pll_id; | 
 | 	fh_regs = fh->fh_regs; | 
 | 	pll_data = fh->pll_data; | 
 |  | 
 | 	if (pll_data->pll_type == FH_PLL_TYPE_NOT_SUPPORT) { | 
 | 		pr_info("%s not support SSC.", pll_data->pll_name); | 
 | 		return -EPERM; | 
 | 	} | 
 |  | 
 | 	pr_debug("fh_pll_id:%d", pll_id); | 
 |  | 
 | 	spin_lock_irqsave(fh->lock, flags); | 
 |  | 
 | 	/* Set the relative registers */ | 
 | 	fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 0); | 
 | 	fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 0); | 
 |  | 
 | 	/* Switch to APMIXEDSYS control */ | 
 | 	fh_set_field(fh_regs->reg_hp_en, BIT(pll_id), | 
 | 				REG_HP_EN_APMIXEDSYS_CTR); | 
 |  | 
 | 	spin_unlock_irqrestore(fh->lock, flags); | 
 |  | 
 | 	/* Wait for DDS to be stable */ | 
 | 	udelay(30); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int clk_mt_fh_hw_pll_ssc_enable(struct clk_mt_fhctl *fh, int ssc_rate) | 
 | { | 
 | 	int pll_id; | 
 | 	unsigned long flags = 0; | 
 | 	unsigned int dds_mask; | 
 | 	unsigned int updnlmt_val; | 
 | 	struct clk_mt_fhctl_regs *fh_regs; | 
 | 	struct clk_mt_fhctl_pll_data *pll_data; | 
 |  | 
 |  | 
 | 	pll_id = fh->pll_data->pll_id; | 
 | 	fh_regs = fh->fh_regs; | 
 | 	pll_data = fh->pll_data; | 
 | 	dds_mask = fh->pll_data->dds_mask; | 
 |  | 
 | 	if (pll_data->pll_type == FH_PLL_TYPE_NOT_SUPPORT) { | 
 | 		pr_info("%s not support SSC.", pll_data->pll_name); | 
 | 		return -EPERM; | 
 | 	} | 
 |  | 
 | 	pr_debug("pll_id:%d ssc:0~-%d%%", pll_id, ssc_rate); | 
 |  | 
 | 	spin_lock_irqsave(fh->lock, flags); | 
 |  | 
 | 	/* Set the relative parameter registers (dt/df/upbnd/downbnd) */ | 
 | 	fh_set_field(fh_regs->reg_cfg, MASK_FRDDSX_DYS, REG_CFG_DF_VAL); | 
 | 	fh_set_field(fh_regs->reg_cfg, MASK_FRDDSX_DTS, REG_CFG_DT_VAL); | 
 |  | 
 | 	writel((readl(fh_regs->reg_con_pcw) & pll_data->dds_mask) | | 
 | 			FH_FHCTLX_PLL_TGL_ORG, fh_regs->reg_dds); | 
 |  | 
 | 	/* Calculate UPDNLMT */ | 
 | 	updnlmt_val = PERCENT_TO_DDSLMT((readl(fh_regs->reg_dds) & | 
 | 					dds_mask), ssc_rate) << 16; | 
 |  | 
 | 	writel(updnlmt_val, fh_regs->reg_updnlmt); | 
 |  | 
 | 	/* Switch to FHCTL_CORE controller - Original design */ | 
 | 	fh_set_field(fh_regs->reg_hp_en, (0x1U << pll_id), | 
 | 					REG_HP_EN_FHCTL_CTR); | 
 |  | 
 | 	/* Enable SSC */ | 
 | 	fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 1); | 
 | 	/* Enable Hopping control */ | 
 | 	fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 1); | 
 |  | 
 | 	/* Keep last ssc rate */ | 
 | 	fh->pll_data->pll_default_ssc_rate = ssc_rate; | 
 |  | 
 | 	spin_unlock_irqrestore(fh->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int clk_mt_fh_hw_pll_hopping(struct clk_mt_fhctl *fh, | 
 | 					unsigned int new_dds, | 
 | 					int postdiv) | 
 | { | 
 | 	int pll_id; | 
 | 	int ret; | 
 | 	unsigned long flags = 0; | 
 | 	unsigned int dds_mask; | 
 | 	unsigned int updnlmt_val; | 
 | 	unsigned int must_restore_ssc = 0; | 
 | 	struct clk_mt_fhctl_regs *fh_regs; | 
 | 	struct clk_mt_fhctl_pll_data *pll_data; | 
 |  | 
 |  | 
 | 	pll_id = fh->pll_data->pll_id; | 
 | 	fh_regs = fh->fh_regs; | 
 | 	pll_data = fh->pll_data; | 
 | 	dds_mask = pll_data->dds_mask; | 
 |  | 
 | 	if ((fh->pll_data->pll_type == FH_PLL_TYPE_NOT_SUPPORT) || | 
 | 		(fh->pll_data->pll_type == FH_PLL_TYPE_CPU)) { | 
 | 		pr_info("%s not support hopping in AP side", | 
 | 						pll_data->pll_name); | 
 | 		return -EPERM; | 
 | 	} | 
 |  | 
 | 	pr_debug("fh_pll_id:%d", pll_id); | 
 |  | 
 | 	spin_lock_irqsave(fh->lock, flags); | 
 |  | 
 | 	/* Check SSC status. */ | 
 | 	fh_get_field(fh_regs->reg_cfg, FH_FRDDSX_EN, must_restore_ssc); | 
 | 	if (must_restore_ssc) { | 
 | 		unsigned int pll_dds = 0; | 
 | 		unsigned int mon_dds = 0; | 
 |  | 
 | 		/* only when SSC is enable, turn off ARMPLL hopping */ | 
 | 		/* disable SSC mode */ | 
 | 		fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 0); | 
 | 		/* disable dvfs mode */ | 
 | 		fh_set_field(fh_regs->reg_cfg, FH_SFSTRX_EN, 0); | 
 | 		/* disable hp ctl */ | 
 | 		fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 0); | 
 |  | 
 | 		pll_dds = (readl(fh_regs->reg_dds)) & pll_data->dds_mask; | 
 |  | 
 | 		/* Wait 1000 us until DDS stable */ | 
 | 		ret = readl_poll_timeout_atomic(fh_regs->reg_mon, mon_dds, | 
 | 			(mon_dds&pll_data->dds_mask) == pll_dds, 10, 1000); | 
 | 		if (ret) | 
 | 			pr_info("ERROR %s: target_dds=0x%x, mon_dds=0x%x", | 
 | 				__func__, pll_dds, mon_dds&pll_data->dds_mask); | 
 |  | 
 | 	} | 
 |  | 
 | 	ret = __mt_fh_hw_hopping(fh, pll_id, new_dds, postdiv); | 
 | 	if (ret) | 
 | 		pr_info("__mt_fh_hw_hopping error:%d", ret); | 
 |  | 
 |  | 
 | 	/* Enable SSC status, if need. */ | 
 | 	if (must_restore_ssc) { | 
 | 		/* disable SSC mode */ | 
 | 		fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 0); | 
 | 		/* disable dvfs mode */ | 
 | 		fh_set_field(fh_regs->reg_cfg, FH_SFSTRX_EN, 0); | 
 | 		/* disable hp ctl */ | 
 | 		fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 0); | 
 |  | 
 | 		fh_set_field(fh_regs->reg_cfg, MASK_FRDDSX_DYS, REG_CFG_DF_VAL); | 
 | 		fh_set_field(fh_regs->reg_cfg, MASK_FRDDSX_DTS, REG_CFG_DT_VAL); | 
 |  | 
 | 		writel((readl(fh_regs->reg_con_pcw) & pll_data->dds_mask) | | 
 | 				FH_FHCTLX_PLL_TGL_ORG, fh_regs->reg_dds); | 
 |  | 
 | 		/* Calculate UPDNLMT */ | 
 | 		updnlmt_val = PERCENT_TO_DDSLMT( | 
 | 				(readl(fh_regs->reg_dds) & dds_mask), | 
 | 				pll_data->pll_default_ssc_rate) << 16; | 
 |  | 
 | 		writel(updnlmt_val, fh_regs->reg_updnlmt); | 
 |  | 
 | 		/* Switch to FHCTL_CORE controller - Original design */ | 
 | 		fh_set_field(fh_regs->reg_hp_en, | 
 | 				(0x1U << pll_id), REG_HP_EN_FHCTL_CTR); | 
 |  | 
 | 		/* enable SSC mode */ | 
 | 		fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 1); | 
 | 		/* enable hopping ctl */ | 
 | 		fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 1); | 
 | 	} | 
 |  | 
 | 	spin_unlock_irqrestore(fh->lock, flags); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | const struct fhctl_ipi_ops ipi_ops = { | 
 | 	.ipi_init = mt_fh_ipi_init, | 
 | }; | 
 |  | 
 | const struct clk_mt_fhctl_hal_ops mt_fhctl_hal_ops = { | 
 | 	.pll_init = clk_mt_fh_hw_pll_init, | 
 | 	.pll_unpause = clk_mt_fh_hw_pll_unpause, | 
 | 	.pll_pause = clk_mt_fh_hw_pll_pause, | 
 | 	.pll_ssc_disable = clk_mt_fh_hw_pll_ssc_disable, | 
 | 	.pll_ssc_enable = clk_mt_fh_hw_pll_ssc_enable, | 
 | 	.pll_hopping = clk_mt_fh_hw_pll_hopping, | 
 | }; | 
 |  |