| // 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, |
| }; |
| |