diff options
Diffstat (limited to 'drivers/clk/msm/clock-pll.c')
| -rw-r--r-- | drivers/clk/msm/clock-pll.c | 1206 |
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); |
