summaryrefslogtreecommitdiff
path: root/drivers/clk/msm/clock-pll.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/msm/clock-pll.c')
-rw-r--r--drivers/clk/msm/clock-pll.c1206
1 files changed, 1206 insertions, 0 deletions
diff --git a/drivers/clk/msm/clock-pll.c b/drivers/clk/msm/clock-pll.c
new file mode 100644
index 000000000000..e4b6df769307
--- /dev/null
+++ b/drivers/clk/msm/clock-pll.c
@@ -0,0 +1,1206 @@
+/*
+ * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <soc/qcom/clock-pll.h>
+#include <soc/qcom/msm-clock-controller.h>
+
+#include "clock.h"
+
+#define PLL_OUTCTRL BIT(0)
+#define PLL_BYPASSNL BIT(1)
+#define PLL_RESET_N BIT(2)
+#define PLL_MODE_MASK BM(3, 0)
+
+#define PLL_EN_REG(x) (*(x)->base + (unsigned long) (x)->en_reg)
+#define PLL_STATUS_REG(x) (*(x)->base + (unsigned long) (x)->status_reg)
+#define PLL_ALT_STATUS_REG(x) (*(x)->base + (unsigned long) \
+ (x)->alt_status_reg)
+#define PLL_MODE_REG(x) (*(x)->base + (unsigned long) (x)->mode_reg)
+#define PLL_L_REG(x) (*(x)->base + (unsigned long) (x)->l_reg)
+#define PLL_M_REG(x) (*(x)->base + (unsigned long) (x)->m_reg)
+#define PLL_N_REG(x) (*(x)->base + (unsigned long) (x)->n_reg)
+#define PLL_CONFIG_REG(x) (*(x)->base + (unsigned long) (x)->config_reg)
+#define PLL_ALPHA_REG(x) (*(x)->base + (unsigned long) (x)->alpha_reg)
+#define PLL_CFG_ALT_REG(x) (*(x)->base + (unsigned long) \
+ (x)->config_alt_reg)
+#define PLL_CFG_CTL_REG(x) (*(x)->base + (unsigned long) \
+ (x)->config_ctl_reg)
+#define PLL_CFG_CTL_HI_REG(x) (*(x)->base + (unsigned long) \
+ (x)->config_ctl_hi_reg)
+#define PLL_TEST_CTL_LO_REG(x) (*(x)->base + (unsigned long) \
+ (x)->test_ctl_lo_reg)
+#define PLL_TEST_CTL_HI_REG(x) (*(x)->base + (unsigned long) \
+ (x)->test_ctl_hi_reg)
+static DEFINE_SPINLOCK(pll_reg_lock);
+
+#define ENABLE_WAIT_MAX_LOOPS 200
+#define PLL_LOCKED_BIT BIT(16)
+
+#define SPM_FORCE_EVENT 0x4
+
+static int pll_vote_clk_enable(struct clk *c)
+{
+ u32 ena, count;
+ unsigned long flags;
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+ ena = readl_relaxed(PLL_EN_REG(pllv));
+ ena |= pllv->en_mask;
+ writel_relaxed(ena, PLL_EN_REG(pllv));
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+
+ /*
+ * Use a memory barrier since some PLL status registers are
+ * not within the same 1K segment as the voting registers.
+ */
+ mb();
+
+ /* Wait for pll to enable. */
+ for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) {
+ if (readl_relaxed(PLL_STATUS_REG(pllv)) & pllv->status_mask)
+ return 0;
+ udelay(1);
+ }
+
+ WARN("PLL %s didn't enable after voting for it!\n", c->dbg_name);
+
+ return -ETIMEDOUT;
+}
+
+static void pll_vote_clk_disable(struct clk *c)
+{
+ u32 ena;
+ unsigned long flags;
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+ ena = readl_relaxed(PLL_EN_REG(pllv));
+ ena &= ~(pllv->en_mask);
+ writel_relaxed(ena, PLL_EN_REG(pllv));
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+}
+
+static int pll_vote_clk_is_enabled(struct clk *c)
+{
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+ return !!(readl_relaxed(PLL_STATUS_REG(pllv)) & pllv->status_mask);
+}
+
+static enum handoff pll_vote_clk_handoff(struct clk *c)
+{
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+ if (readl_relaxed(PLL_EN_REG(pllv)) & pllv->en_mask)
+ return HANDOFF_ENABLED_CLK;
+
+ return HANDOFF_DISABLED_CLK;
+}
+
+static void __iomem *pll_vote_clk_list_registers(struct clk *c, int n,
+ struct clk_register_data **regs, u32 *size)
+{
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+ static struct clk_register_data data1[] = {
+ {"APPS_VOTE", 0x0},
+ };
+
+ if (n)
+ return ERR_PTR(-EINVAL);
+
+ *regs = data1;
+ *size = ARRAY_SIZE(data1);
+ return PLL_EN_REG(pllv);
+}
+
+struct clk_ops clk_ops_pll_vote = {
+ .enable = pll_vote_clk_enable,
+ .disable = pll_vote_clk_disable,
+ .is_enabled = pll_vote_clk_is_enabled,
+ .handoff = pll_vote_clk_handoff,
+ .list_registers = pll_vote_clk_list_registers,
+};
+
+/*
+ * spm_event() -- Set/Clear SPM events
+ * PLL off sequence -- enable (1)
+ * Set L2_SPM_FORCE_EVENT_EN[bit] register to 1
+ * Set L2_SPM_FORCE_EVENT[bit] register to 1
+ * PLL on sequence -- enable (0)
+ * Clear L2_SPM_FORCE_EVENT[bit] register to 0
+ * Clear L2_SPM_FORCE_EVENT_EN[bit] register to 0
+ */
+static void spm_event(void __iomem *base, u32 offset, u32 bit,
+ bool enable)
+{
+ uint32_t val;
+
+ if (!base)
+ return;
+
+ if (enable) {
+ /* L2_SPM_FORCE_EVENT_EN */
+ val = readl_relaxed(base + offset);
+ val |= BIT(bit);
+ writel_relaxed(val, (base + offset));
+ /* Ensure that the write above goes through. */
+ mb();
+
+ /* L2_SPM_FORCE_EVENT */
+ val = readl_relaxed(base + offset + SPM_FORCE_EVENT);
+ val |= BIT(bit);
+ writel_relaxed(val, (base + offset + SPM_FORCE_EVENT));
+ /* Ensure that the write above goes through. */
+ mb();
+ } else {
+ /* L2_SPM_FORCE_EVENT */
+ val = readl_relaxed(base + offset + SPM_FORCE_EVENT);
+ val &= ~BIT(bit);
+ writel_relaxed(val, (base + offset + SPM_FORCE_EVENT));
+ /* Ensure that the write above goes through. */
+ mb();
+
+ /* L2_SPM_FORCE_EVENT_EN */
+ val = readl_relaxed(base + offset);
+ val &= ~BIT(bit);
+ writel_relaxed(val, (base + offset));
+ /* Ensure that the write above goes through. */
+ mb();
+ }
+}
+
+static void __pll_config_reg(void __iomem *pll_config, struct pll_freq_tbl *f,
+ struct pll_config_masks *masks)
+{
+ u32 regval;
+
+ regval = readl_relaxed(pll_config);
+
+ /* Enable the MN counter if used */
+ if (f->m_val)
+ regval |= masks->mn_en_mask;
+
+ /* Set pre-divider and post-divider values */
+ regval &= ~masks->pre_div_mask;
+ regval |= f->pre_div_val;
+ regval &= ~masks->post_div_mask;
+ regval |= f->post_div_val;
+
+ /* Select VCO setting */
+ regval &= ~masks->vco_mask;
+ regval |= f->vco_val;
+
+ /* Enable main output if it has not been enabled */
+ if (masks->main_output_mask && !(regval & masks->main_output_mask))
+ regval |= masks->main_output_mask;
+
+ writel_relaxed(regval, pll_config);
+}
+
+static int sr2_pll_clk_enable(struct clk *c)
+{
+ unsigned long flags;
+ struct pll_clk *pll = to_pll_clk(c);
+ int ret = 0, count;
+ u32 mode = readl_relaxed(PLL_MODE_REG(pll));
+ u32 lockmask = pll->masks.lock_mask ?: PLL_LOCKED_BIT;
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+
+ spm_event(pll->spm_ctrl.spm_base, pll->spm_ctrl.offset,
+ pll->spm_ctrl.event_bit, false);
+
+ /* Disable PLL bypass mode. */
+ mode |= PLL_BYPASSNL;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /*
+ * H/W requires a 5us delay between disabling the bypass and
+ * de-asserting the reset. Delay 10us just to be safe.
+ */
+ mb();
+ udelay(10);
+
+ /* De-assert active-low PLL reset. */
+ mode |= PLL_RESET_N;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /* Wait for pll to lock. */
+ for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) {
+ if (readl_relaxed(PLL_STATUS_REG(pll)) & lockmask)
+ break;
+ udelay(1);
+ }
+
+ if (!(readl_relaxed(PLL_STATUS_REG(pll)) & lockmask))
+ pr_err("PLL %s didn't lock after enabling it!\n", c->dbg_name);
+
+ /* Enable PLL output. */
+ mode |= PLL_OUTCTRL;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /* Ensure that the write above goes through before returning. */
+ mb();
+
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+ return ret;
+}
+
+void __variable_rate_pll_init(struct clk *c)
+{
+ struct pll_clk *pll = to_pll_clk(c);
+ u32 regval;
+
+ regval = readl_relaxed(PLL_CONFIG_REG(pll));
+
+ if (pll->masks.post_div_mask) {
+ regval &= ~pll->masks.post_div_mask;
+ regval |= pll->vals.post_div_masked;
+ }
+
+ if (pll->masks.pre_div_mask) {
+ regval &= ~pll->masks.pre_div_mask;
+ regval |= pll->vals.pre_div_masked;
+ }
+
+ if (pll->masks.main_output_mask)
+ regval |= pll->masks.main_output_mask;
+
+ if (pll->masks.early_output_mask)
+ regval |= pll->masks.early_output_mask;
+
+ if (pll->vals.enable_mn)
+ regval |= pll->masks.mn_en_mask;
+ else
+ regval &= ~pll->masks.mn_en_mask;
+
+ writel_relaxed(regval, PLL_CONFIG_REG(pll));
+
+ regval = readl_relaxed(PLL_MODE_REG(pll));
+ if (pll->masks.apc_pdn_mask)
+ regval &= ~pll->masks.apc_pdn_mask;
+ writel_relaxed(regval, PLL_MODE_REG(pll));
+
+ writel_relaxed(pll->vals.alpha_val, PLL_ALPHA_REG(pll));
+ writel_relaxed(pll->vals.config_ctl_val, PLL_CFG_CTL_REG(pll));
+ if (pll->vals.config_ctl_hi_val)
+ writel_relaxed(pll->vals.config_ctl_hi_val,
+ PLL_CFG_CTL_HI_REG(pll));
+ if (pll->init_test_ctl) {
+ writel_relaxed(pll->vals.test_ctl_lo_val,
+ PLL_TEST_CTL_LO_REG(pll));
+ writel_relaxed(pll->vals.test_ctl_hi_val,
+ PLL_TEST_CTL_HI_REG(pll));
+ }
+
+ pll->inited = true;
+}
+
+static int variable_rate_pll_clk_enable(struct clk *c)
+{
+ unsigned long flags;
+ struct pll_clk *pll = to_pll_clk(c);
+ int ret = 0, count;
+ u32 mode, testlo;
+ u32 lockmask = pll->masks.lock_mask ?: PLL_LOCKED_BIT;
+ u32 mode_lock;
+ u64 time;
+ bool early_lock = false;
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+
+ if (unlikely(!to_pll_clk(c)->inited))
+ __variable_rate_pll_init(c);
+
+ mode = readl_relaxed(PLL_MODE_REG(pll));
+
+ /* Set test control bits as required by HW doc */
+ if (pll->test_ctl_lo_reg && pll->vals.test_ctl_lo_val &&
+ pll->pgm_test_ctl_enable)
+ writel_relaxed(pll->vals.test_ctl_lo_val,
+ PLL_TEST_CTL_LO_REG(pll));
+
+ if (!pll->test_ctl_dbg) {
+ /* Enable test_ctl debug */
+ mode |= BIT(3);
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll));
+ testlo &= ~BM(7, 6);
+ testlo |= 0xC0;
+ writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll));
+ /* Wait for the write to complete */
+ mb();
+ }
+
+ /* Disable PLL bypass mode. */
+ mode |= PLL_BYPASSNL;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /*
+ * H/W requires a 5us delay between disabling the bypass and
+ * de-asserting the reset. Use 10us to be sure.
+ */
+ mb();
+ udelay(10);
+
+ /* De-assert active-low PLL reset. */
+ mode |= PLL_RESET_N;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /*
+ * 5us delay mandated by HPG. However, put in a 200us delay here.
+ * This is to address possible locking issues with the PLL exhibit
+ * early "transient" locks about 16us from this point. With this
+ * higher delay, we avoid running into those transients.
+ */
+ mb();
+ udelay(200);
+
+ /* Clear test control bits */
+ if (pll->test_ctl_lo_reg && pll->vals.test_ctl_lo_val &&
+ pll->pgm_test_ctl_enable)
+ writel_relaxed(0x0, PLL_TEST_CTL_LO_REG(pll));
+
+
+ time = sched_clock();
+ /* Wait for pll to lock. */
+ for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) {
+ if (readl_relaxed(PLL_STATUS_REG(pll)) & lockmask) {
+ udelay(1);
+ /*
+ * Check again to be sure. This is to avoid
+ * breaking too early if there is a "transient"
+ * lock.
+ */
+ if ((readl_relaxed(PLL_STATUS_REG(pll)) & lockmask))
+ break;
+ else
+ early_lock = true;
+ }
+ udelay(1);
+ }
+ time = sched_clock() - time;
+
+ mode_lock = readl_relaxed(PLL_STATUS_REG(pll));
+
+ if (!(mode_lock & lockmask)) {
+ pr_err("PLL lock bit detection total wait time: %lld ns", time);
+ pr_err("PLL %s didn't lock after enabling for L value 0x%x!\n",
+ c->dbg_name, readl_relaxed(PLL_L_REG(pll)));
+ pr_err("mode register is 0x%x\n",
+ readl_relaxed(PLL_STATUS_REG(pll)));
+ pr_err("user control register is 0x%x\n",
+ readl_relaxed(PLL_CONFIG_REG(pll)));
+ pr_err("config control register is 0x%x\n",
+ readl_relaxed(PLL_CFG_CTL_REG(pll)));
+ pr_err("test control high register is 0x%x\n",
+ readl_relaxed(PLL_TEST_CTL_HI_REG(pll)));
+ pr_err("test control low register is 0x%x\n",
+ readl_relaxed(PLL_TEST_CTL_LO_REG(pll)));
+ pr_err("early lock? %s\n", early_lock ? "yes" : "no");
+
+ testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll));
+ testlo &= ~BM(7, 6);
+ writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll));
+ /* Wait for the write to complete */
+ mb();
+
+ pr_err("test_ctl_lo = 0x%x, pll status is: 0x%x\n",
+ readl_relaxed(PLL_TEST_CTL_LO_REG(pll)),
+ readl_relaxed(PLL_ALT_STATUS_REG(pll)));
+
+ testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll));
+ testlo &= ~BM(7, 6);
+ testlo |= 0x40;
+ writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll));
+ /* Wait for the write to complete */
+ mb();
+ pr_err("test_ctl_lo = 0x%x, pll status is: 0x%x\n",
+ readl_relaxed(PLL_TEST_CTL_LO_REG(pll)),
+ readl_relaxed(PLL_ALT_STATUS_REG(pll)));
+
+ testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll));
+ testlo &= ~BM(7, 6);
+ testlo |= 0x80;
+ writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll));
+ /* Wait for the write to complete */
+ mb();
+
+ pr_err("test_ctl_lo = 0x%x, pll status is: 0x%x\n",
+ readl_relaxed(PLL_TEST_CTL_LO_REG(pll)),
+ readl_relaxed(PLL_ALT_STATUS_REG(pll)));
+
+ testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll));
+ testlo &= ~BM(7, 6);
+ testlo |= 0xC0;
+ writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll));
+ /* Wait for the write to complete */
+ mb();
+
+ pr_err("test_ctl_lo = 0x%x, pll status is: 0x%x\n",
+ readl_relaxed(PLL_TEST_CTL_LO_REG(pll)),
+ readl_relaxed(PLL_ALT_STATUS_REG(pll)));
+ panic("failed to lock %s PLL\n", c->dbg_name);
+ }
+
+ /* Enable PLL output. */
+ mode |= PLL_OUTCTRL;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /* Ensure that the write above goes through before returning. */
+ mb();
+
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+
+ return ret;
+}
+
+static void variable_rate_pll_clk_disable_hwfsm(struct clk *c)
+{
+ struct pll_clk *pll = to_pll_clk(c);
+ u32 regval;
+
+ /* Set test control bit to stay-in-CFA if necessary */
+ if (pll->test_ctl_lo_reg && pll->pgm_test_ctl_enable) {
+ regval = readl_relaxed(PLL_TEST_CTL_LO_REG(pll));
+ writel_relaxed(regval | BIT(16),
+ PLL_TEST_CTL_LO_REG(pll));
+ }
+
+ /* 8 reference clock cycle delay mandated by the HPG */
+ udelay(1);
+}
+
+static int variable_rate_pll_clk_enable_hwfsm(struct clk *c)
+{
+ struct pll_clk *pll = to_pll_clk(c);
+ int count;
+ u32 lockmask = pll->masks.lock_mask ?: PLL_LOCKED_BIT;
+ unsigned long flags;
+ u32 regval;
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+
+ /* Clear test control bit if necessary */
+ if (pll->test_ctl_lo_reg && pll->pgm_test_ctl_enable) {
+ regval = readl_relaxed(PLL_TEST_CTL_LO_REG(pll));
+ regval &= ~BIT(16);
+ writel_relaxed(regval, PLL_TEST_CTL_LO_REG(pll));
+ }
+
+ /* Wait for 50us explicitly to avoid transient locks */
+ udelay(50);
+
+ for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) {
+ if (readl_relaxed(PLL_STATUS_REG(pll)) & lockmask)
+ break;
+ udelay(1);
+ }
+
+ if (!(readl_relaxed(PLL_STATUS_REG(pll)) & lockmask))
+ pr_err("PLL %s didn't lock after enabling it!\n", c->dbg_name);
+
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+
+ return 0;
+}
+
+static void __pll_clk_enable_reg(void __iomem *mode_reg)
+{
+ u32 mode = readl_relaxed(mode_reg);
+ /* Disable PLL bypass mode. */
+ mode |= PLL_BYPASSNL;
+ writel_relaxed(mode, mode_reg);
+
+ /*
+ * H/W requires a 5us delay between disabling the bypass and
+ * de-asserting the reset. Delay 10us just to be safe.
+ */
+ mb();
+ udelay(10);
+
+ /* De-assert active-low PLL reset. */
+ mode |= PLL_RESET_N;
+ writel_relaxed(mode, mode_reg);
+
+ /* Wait until PLL is locked. */
+ mb();
+ udelay(50);
+
+ /* Enable PLL output. */
+ mode |= PLL_OUTCTRL;
+ writel_relaxed(mode, mode_reg);
+
+ /* Ensure that the write above goes through before returning. */
+ mb();
+}
+
+static int local_pll_clk_enable(struct clk *c)
+{
+ unsigned long flags;
+ struct pll_clk *pll = to_pll_clk(c);
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+ __pll_clk_enable_reg(PLL_MODE_REG(pll));
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+
+ return 0;
+}
+
+static void __pll_clk_disable_reg(void __iomem *mode_reg)
+{
+ u32 mode = readl_relaxed(mode_reg);
+ mode &= ~PLL_MODE_MASK;
+ writel_relaxed(mode, mode_reg);
+}
+
+static void local_pll_clk_disable(struct clk *c)
+{
+ unsigned long flags;
+ struct pll_clk *pll = to_pll_clk(c);
+
+ /*
+ * Disable the PLL output, disable test mode, enable
+ * the bypass mode, and assert the reset.
+ */
+ spin_lock_irqsave(&pll_reg_lock, flags);
+ spm_event(pll->spm_ctrl.spm_base, pll->spm_ctrl.offset,
+ pll->spm_ctrl.event_bit, true);
+ __pll_clk_disable_reg(PLL_MODE_REG(pll));
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+}
+
+static enum handoff local_pll_clk_handoff(struct clk *c)
+{
+ struct pll_clk *pll = to_pll_clk(c);
+ u32 mode = readl_relaxed(PLL_MODE_REG(pll));
+ u32 mask = PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL;
+ unsigned long parent_rate;
+ u32 lval, mval, nval, userval;
+
+ if ((mode & mask) != mask)
+ return HANDOFF_DISABLED_CLK;
+
+ /* Assume bootloaders configure PLL to c->rate */
+ if (c->rate)
+ return HANDOFF_ENABLED_CLK;
+
+ parent_rate = clk_get_rate(c->parent);
+ lval = readl_relaxed(PLL_L_REG(pll));
+ mval = readl_relaxed(PLL_M_REG(pll));
+ nval = readl_relaxed(PLL_N_REG(pll));
+ userval = readl_relaxed(PLL_CONFIG_REG(pll));
+
+ c->rate = parent_rate * lval;
+
+ if (pll->masks.mn_en_mask && userval) {
+ if (!nval)
+ nval = 1;
+ c->rate += (parent_rate * mval) / nval;
+ }
+
+ return HANDOFF_ENABLED_CLK;
+}
+
+static long local_pll_clk_round_rate(struct clk *c, unsigned long rate)
+{
+ struct pll_freq_tbl *nf;
+ struct pll_clk *pll = to_pll_clk(c);
+
+ if (!pll->freq_tbl)
+ return -EINVAL;
+
+ for (nf = pll->freq_tbl; nf->freq_hz != PLL_FREQ_END; nf++)
+ if (nf->freq_hz >= rate)
+ return nf->freq_hz;
+
+ nf--;
+ return nf->freq_hz;
+}
+
+static int local_pll_clk_set_rate(struct clk *c, unsigned long rate)
+{
+ struct pll_freq_tbl *nf;
+ struct pll_clk *pll = to_pll_clk(c);
+ unsigned long flags;
+
+ for (nf = pll->freq_tbl; nf->freq_hz != PLL_FREQ_END
+ && nf->freq_hz != rate; nf++)
+ ;
+
+ if (nf->freq_hz == PLL_FREQ_END)
+ return -EINVAL;
+
+ /*
+ * Ensure PLL is off before changing rate. For optimization reasons,
+ * assume no downstream clock is using actively using it.
+ */
+ spin_lock_irqsave(&c->lock, flags);
+ if (c->count)
+ c->ops->disable(c);
+
+ writel_relaxed(nf->l_val, PLL_L_REG(pll));
+ writel_relaxed(nf->m_val, PLL_M_REG(pll));
+ writel_relaxed(nf->n_val, PLL_N_REG(pll));
+
+ __pll_config_reg(PLL_CONFIG_REG(pll), nf, &pll->masks);
+
+ if (c->count)
+ c->ops->enable(c);
+
+ spin_unlock_irqrestore(&c->lock, flags);
+ return 0;
+}
+
+static enum handoff variable_rate_pll_handoff(struct clk *c)
+{
+ struct pll_clk *pll = to_pll_clk(c);
+ u32 mode = readl_relaxed(PLL_MODE_REG(pll));
+ u32 mask = PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL;
+ u32 lval;
+
+ pll->src_rate = clk_get_rate(c->parent);
+
+ lval = readl_relaxed(PLL_L_REG(pll));
+ if (!lval)
+ return HANDOFF_DISABLED_CLK;
+
+ c->rate = pll->src_rate * lval;
+
+ if (c->rate > pll->max_rate || c->rate < pll->min_rate) {
+ WARN(1, "%s: Out of spec PLL", c->dbg_name);
+ return HANDOFF_DISABLED_CLK;
+ }
+
+ if ((mode & mask) != mask)
+ return HANDOFF_DISABLED_CLK;
+
+ return HANDOFF_ENABLED_CLK;
+}
+
+static long variable_rate_pll_round_rate(struct clk *c, unsigned long rate)
+{
+ struct pll_clk *pll = to_pll_clk(c);
+
+ if (!pll->src_rate)
+ return 0;
+
+ if (pll->no_prepared_reconfig && c->prepare_count && c->rate != rate)
+ return -EINVAL;
+
+ if (rate < pll->min_rate)
+ rate = pll->min_rate;
+ if (rate > pll->max_rate)
+ rate = pll->max_rate;
+
+ return min(pll->max_rate,
+ DIV_ROUND_UP(rate, pll->src_rate) * pll->src_rate);
+}
+
+/*
+ * For optimization reasons, assumes no downstream clocks are actively using
+ * it.
+ */
+static int variable_rate_pll_set_rate(struct clk *c, unsigned long rate)
+{
+ struct pll_clk *pll = to_pll_clk(c);
+ unsigned long flags;
+ u32 l_val;
+
+ if (rate != variable_rate_pll_round_rate(c, rate))
+ return -EINVAL;
+
+ l_val = rate / pll->src_rate;
+
+ spin_lock_irqsave(&c->lock, flags);
+
+ if (c->count && c->ops->disable)
+ c->ops->disable(c);
+
+ writel_relaxed(l_val, PLL_L_REG(pll));
+
+ if (c->count && c->ops->enable)
+ c->ops->enable(c);
+
+ spin_unlock_irqrestore(&c->lock, flags);
+
+ return 0;
+}
+
+int sr_pll_clk_enable(struct clk *c)
+{
+ u32 mode;
+ unsigned long flags;
+ struct pll_clk *pll = to_pll_clk(c);
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+ mode = readl_relaxed(PLL_MODE_REG(pll));
+ /* De-assert active-low PLL reset. */
+ mode |= PLL_RESET_N;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /*
+ * H/W requires a 5us delay between disabling the bypass and
+ * de-asserting the reset. Delay 10us just to be safe.
+ */
+ mb();
+ udelay(10);
+
+ /* Disable PLL bypass mode. */
+ mode |= PLL_BYPASSNL;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /* Wait until PLL is locked. */
+ mb();
+ udelay(60);
+
+ /* Enable PLL output. */
+ mode |= PLL_OUTCTRL;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /* Ensure that the write above goes through before returning. */
+ mb();
+
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+
+ return 0;
+}
+
+int sr_hpm_lp_pll_clk_enable(struct clk *c)
+{
+ unsigned long flags;
+ struct pll_clk *pll = to_pll_clk(c);
+ u32 count, mode;
+ int ret = 0;
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+
+ /* Disable PLL bypass mode and de-assert reset. */
+ mode = PLL_BYPASSNL | PLL_RESET_N;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /* Wait for pll to lock. */
+ for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) {
+ if (readl_relaxed(PLL_STATUS_REG(pll)) & PLL_LOCKED_BIT)
+ break;
+ udelay(1);
+ }
+
+ if (!(readl_relaxed(PLL_STATUS_REG(pll)) & PLL_LOCKED_BIT)) {
+ WARN("PLL %s didn't lock after enabling it!\n", c->dbg_name);
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ /* Enable PLL output. */
+ mode |= PLL_OUTCTRL;
+ writel_relaxed(mode, PLL_MODE_REG(pll));
+
+ /* Ensure the write above goes through before returning. */
+ mb();
+
+out:
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+ return ret;
+}
+
+
+static void __iomem *variable_rate_pll_list_registers(struct clk *c, int n,
+ struct clk_register_data **regs, u32 *size)
+{
+ struct pll_clk *pll = to_pll_clk(c);
+ static struct clk_register_data data[] = {
+ {"MODE", 0x0},
+ {"L", 0x4},
+ {"ALPHA", 0x8},
+ {"USER_CTL", 0x10},
+ {"CONFIG_CTL", 0x14},
+ {"STATUS", 0x1C},
+ };
+ if (n)
+ return ERR_PTR(-EINVAL);
+
+ *regs = data;
+ *size = ARRAY_SIZE(data);
+ return PLL_MODE_REG(pll);
+}
+
+static void __iomem *local_pll_clk_list_registers(struct clk *c, int n,
+ struct clk_register_data **regs, u32 *size)
+{
+ /* Not compatible with 8960 & friends */
+ struct pll_clk *pll = to_pll_clk(c);
+ static struct clk_register_data data[] = {
+ {"MODE", 0x0},
+ {"L", 0x4},
+ {"M", 0x8},
+ {"N", 0xC},
+ {"USER", 0x10},
+ {"CONFIG", 0x14},
+ {"STATUS", 0x1C},
+ };
+ if (n)
+ return ERR_PTR(-EINVAL);
+
+ *regs = data;
+ *size = ARRAY_SIZE(data);
+ return PLL_MODE_REG(pll);
+}
+
+
+struct clk_ops clk_ops_local_pll = {
+ .enable = local_pll_clk_enable,
+ .disable = local_pll_clk_disable,
+ .set_rate = local_pll_clk_set_rate,
+ .handoff = local_pll_clk_handoff,
+ .list_registers = local_pll_clk_list_registers,
+};
+
+struct clk_ops clk_ops_sr2_pll = {
+ .enable = sr2_pll_clk_enable,
+ .disable = local_pll_clk_disable,
+ .set_rate = local_pll_clk_set_rate,
+ .round_rate = local_pll_clk_round_rate,
+ .handoff = local_pll_clk_handoff,
+ .list_registers = local_pll_clk_list_registers,
+};
+
+struct clk_ops clk_ops_variable_rate_pll_hwfsm = {
+ .enable = variable_rate_pll_clk_enable_hwfsm,
+ .disable = variable_rate_pll_clk_disable_hwfsm,
+ .set_rate = variable_rate_pll_set_rate,
+ .round_rate = variable_rate_pll_round_rate,
+ .handoff = variable_rate_pll_handoff,
+};
+
+struct clk_ops clk_ops_variable_rate_pll = {
+ .enable = variable_rate_pll_clk_enable,
+ .disable = local_pll_clk_disable,
+ .set_rate = variable_rate_pll_set_rate,
+ .round_rate = variable_rate_pll_round_rate,
+ .handoff = variable_rate_pll_handoff,
+ .list_registers = variable_rate_pll_list_registers,
+};
+
+static DEFINE_SPINLOCK(soft_vote_lock);
+
+static int pll_acpu_vote_clk_enable(struct clk *c)
+{
+ int ret = 0;
+ unsigned long flags;
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+
+ spin_lock_irqsave(&soft_vote_lock, flags);
+
+ if (!*pllv->soft_vote)
+ ret = pll_vote_clk_enable(c);
+ if (ret == 0)
+ *pllv->soft_vote |= (pllv->soft_vote_mask);
+
+ spin_unlock_irqrestore(&soft_vote_lock, flags);
+ return ret;
+}
+
+static void pll_acpu_vote_clk_disable(struct clk *c)
+{
+ unsigned long flags;
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+
+ spin_lock_irqsave(&soft_vote_lock, flags);
+
+ *pllv->soft_vote &= ~(pllv->soft_vote_mask);
+ if (!*pllv->soft_vote)
+ pll_vote_clk_disable(c);
+
+ spin_unlock_irqrestore(&soft_vote_lock, flags);
+}
+
+static enum handoff pll_acpu_vote_clk_handoff(struct clk *c)
+{
+ if (pll_vote_clk_handoff(c) == HANDOFF_DISABLED_CLK)
+ return HANDOFF_DISABLED_CLK;
+
+ if (pll_acpu_vote_clk_enable(c))
+ return HANDOFF_DISABLED_CLK;
+
+ return HANDOFF_ENABLED_CLK;
+}
+
+struct clk_ops clk_ops_pll_acpu_vote = {
+ .enable = pll_acpu_vote_clk_enable,
+ .disable = pll_acpu_vote_clk_disable,
+ .is_enabled = pll_vote_clk_is_enabled,
+ .handoff = pll_acpu_vote_clk_handoff,
+ .list_registers = pll_vote_clk_list_registers,
+};
+
+
+static int pll_sleep_clk_enable(struct clk *c)
+{
+ u32 ena;
+ unsigned long flags;
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+ ena = readl_relaxed(PLL_EN_REG(pllv));
+ ena &= ~(pllv->en_mask);
+ writel_relaxed(ena, PLL_EN_REG(pllv));
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+ return 0;
+}
+
+static void pll_sleep_clk_disable(struct clk *c)
+{
+ u32 ena;
+ unsigned long flags;
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+
+ spin_lock_irqsave(&pll_reg_lock, flags);
+ ena = readl_relaxed(PLL_EN_REG(pllv));
+ ena |= pllv->en_mask;
+ writel_relaxed(ena, PLL_EN_REG(pllv));
+ spin_unlock_irqrestore(&pll_reg_lock, flags);
+}
+
+static enum handoff pll_sleep_clk_handoff(struct clk *c)
+{
+ struct pll_vote_clk *pllv = to_pll_vote_clk(c);
+
+ if (!(readl_relaxed(PLL_EN_REG(pllv)) & pllv->en_mask))
+ return HANDOFF_ENABLED_CLK;
+
+ return HANDOFF_DISABLED_CLK;
+}
+
+/*
+ * This .ops is meant to be used by gpll0_sleep_clk_src. The aim is to utilise
+ * the h/w feature of sleep enable bit to denote if the PLL can be turned OFF
+ * once APPS goes to PC. gpll0_sleep_clk_src will be enabled only if there is a
+ * peripheral client using it and disabled if there is none. The current
+ * implementation of enable .ops clears the h/w bit of sleep enable while the
+ * disable .ops asserts it.
+ */
+
+struct clk_ops clk_ops_pll_sleep_vote = {
+ .enable = pll_sleep_clk_enable,
+ .disable = pll_sleep_clk_disable,
+ .handoff = pll_sleep_clk_handoff,
+ .list_registers = pll_vote_clk_list_registers,
+};
+
+static void __set_fsm_mode(void __iomem *mode_reg,
+ u32 bias_count, u32 lock_count)
+{
+ u32 regval = readl_relaxed(mode_reg);
+
+ /* De-assert reset to FSM */
+ regval &= ~BIT(21);
+ writel_relaxed(regval, mode_reg);
+
+ /* Program bias count */
+ regval &= ~BM(19, 14);
+ regval |= BVAL(19, 14, bias_count);
+ writel_relaxed(regval, mode_reg);
+
+ /* Program lock count */
+ regval &= ~BM(13, 8);
+ regval |= BVAL(13, 8, lock_count);
+ writel_relaxed(regval, mode_reg);
+
+ /* Enable PLL FSM voting */
+ regval |= BIT(20);
+ writel_relaxed(regval, mode_reg);
+}
+
+static void __configure_alt_config(struct pll_alt_config config,
+ struct pll_config_regs *regs)
+{
+ u32 regval;
+
+ regval = readl_relaxed(PLL_CFG_ALT_REG(regs));
+
+ if (config.mask) {
+ regval &= ~config.mask;
+ regval |= config.val;
+ }
+
+ writel_relaxed(regval, PLL_CFG_ALT_REG(regs));
+}
+
+void __configure_pll(struct pll_config *config,
+ struct pll_config_regs *regs, u32 ena_fsm_mode)
+{
+ u32 regval;
+
+ writel_relaxed(config->l, PLL_L_REG(regs));
+ writel_relaxed(config->m, PLL_M_REG(regs));
+ writel_relaxed(config->n, PLL_N_REG(regs));
+
+ regval = readl_relaxed(PLL_CONFIG_REG(regs));
+
+ /* Enable the MN accumulator */
+ if (config->mn_ena_mask) {
+ regval &= ~config->mn_ena_mask;
+ regval |= config->mn_ena_val;
+ }
+
+ /* Enable the main output */
+ if (config->main_output_mask) {
+ regval &= ~config->main_output_mask;
+ regval |= config->main_output_val;
+ }
+
+ /* Enable the aux output */
+ if (config->aux_output_mask) {
+ regval &= ~config->aux_output_mask;
+ regval |= config->aux_output_val;
+ }
+
+ /* Set pre-divider and post-divider values */
+ regval &= ~config->pre_div_mask;
+ regval |= config->pre_div_val;
+ regval &= ~config->post_div_mask;
+ regval |= config->post_div_val;
+
+ /* Select VCO setting */
+ regval &= ~config->vco_mask;
+ regval |= config->vco_val;
+
+ if (config->add_factor_mask) {
+ regval &= ~config->add_factor_mask;
+ regval |= config->add_factor_val;
+ }
+
+ writel_relaxed(regval, PLL_CONFIG_REG(regs));
+
+ if (regs->config_alt_reg)
+ __configure_alt_config(config->alt_cfg, regs);
+
+ if (regs->config_ctl_reg)
+ writel_relaxed(config->cfg_ctl_val, PLL_CFG_CTL_REG(regs));
+}
+
+void configure_sr_pll(struct pll_config *config,
+ struct pll_config_regs *regs, u32 ena_fsm_mode)
+{
+ __configure_pll(config, regs, ena_fsm_mode);
+ if (ena_fsm_mode)
+ __set_fsm_mode(PLL_MODE_REG(regs), 0x1, 0x8);
+}
+
+void configure_sr_hpm_lp_pll(struct pll_config *config,
+ struct pll_config_regs *regs, u32 ena_fsm_mode)
+{
+ __configure_pll(config, regs, ena_fsm_mode);
+ if (ena_fsm_mode)
+ __set_fsm_mode(PLL_MODE_REG(regs), 0x1, 0x0);
+}
+
+static void *votable_pll_clk_dt_parser(struct device *dev,
+ struct device_node *np)
+{
+ struct pll_vote_clk *v, *peer;
+ struct clk *c;
+ u32 val, rc;
+ phandle p;
+ struct msmclk_data *drv;
+
+ v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL);
+ if (!v) {
+ dt_err(np, "memory alloc failure\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ drv = msmclk_parse_phandle(dev, np->parent->phandle);
+ if (IS_ERR_OR_NULL(drv))
+ return ERR_CAST(drv);
+ v->base = &drv->base;
+
+ rc = of_property_read_u32(np, "qcom,en-offset", (u32 *)&v->en_reg);
+ if (rc) {
+ dt_err(np, "missing qcom,en-offset dt property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ rc = of_property_read_u32(np, "qcom,en-bit", &val);
+ if (rc) {
+ dt_err(np, "missing qcom,en-bit dt property\n");
+ return ERR_PTR(-EINVAL);
+ }
+ v->en_mask = BIT(val);
+
+ rc = of_property_read_u32(np, "qcom,status-offset",
+ (u32 *)&v->status_reg);
+ if (rc) {
+ dt_err(np, "missing qcom,status-offset dt property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ rc = of_property_read_u32(np, "qcom,status-bit", &val);
+ if (rc) {
+ dt_err(np, "missing qcom,status-bit dt property\n");
+ return ERR_PTR(-EINVAL);
+ }
+ v->status_mask = BIT(val);
+
+ rc = of_property_read_u32(np, "qcom,pll-config-rate", &val);
+ if (rc) {
+ dt_err(np, "missing qcom,pll-config-rate dt property\n");
+ return ERR_PTR(-EINVAL);
+ }
+ v->c.rate = val;
+
+ if (of_device_is_compatible(np, "qcom,active-only-pll"))
+ v->soft_vote_mask = PLL_SOFT_VOTE_ACPU;
+ else if (of_device_is_compatible(np, "qcom,sleep-active-pll"))
+ v->soft_vote_mask = PLL_SOFT_VOTE_PRIMARY;
+
+ if (of_device_is_compatible(np, "qcom,votable-pll")) {
+ v->c.ops = &clk_ops_pll_vote;
+ return msmclk_generic_clk_init(dev, np, &v->c);
+ }
+
+ rc = of_property_read_phandle_index(np, "qcom,peer", 0, &p);
+ if (rc) {
+ dt_err(np, "missing qcom,peer dt property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ c = msmclk_lookup_phandle(dev, p);
+ if (!IS_ERR_OR_NULL(c)) {
+ v->soft_vote = devm_kzalloc(dev, sizeof(*v->soft_vote),
+ GFP_KERNEL);
+ if (!v->soft_vote) {
+ dt_err(np, "memory alloc failure\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ peer = to_pll_vote_clk(c);
+ peer->soft_vote = v->soft_vote;
+ }
+
+ v->c.ops = &clk_ops_pll_acpu_vote;
+ return msmclk_generic_clk_init(dev, np, &v->c);
+}
+MSMCLK_PARSER(votable_pll_clk_dt_parser, "qcom,active-only-pll", 0);
+MSMCLK_PARSER(votable_pll_clk_dt_parser, "qcom,sleep-active-pll", 1);
+MSMCLK_PARSER(votable_pll_clk_dt_parser, "qcom,votable-pll", 2);