[Feature] add GA346 baseline version
Change-Id: Ic62933698569507dcf98240cdf5d9931ae34348f
diff --git a/src/kernel/linux/v4.19/drivers/clk/mediatek/clk-fhctl-ap.c b/src/kernel/linux/v4.19/drivers/clk/mediatek/clk-fhctl-ap.c
new file mode 100644
index 0000000..78c6ac5
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/clk/mediatek/clk-fhctl-ap.c
@@ -0,0 +1,398 @@
+// 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,
+};
+