1208 lines
30 KiB
C
1208 lines
30 KiB
C
/*
|
|
* Copyright (c) 2012-2015, 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,
|
|
.list_registers = variable_rate_pll_list_registers,
|
|
};
|
|
|
|
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);
|