Rajendra Nayak 400d9fda39 clk: qcom: Enable FSM mode for votable alpha PLLs
The votable alpha PLLs need to have the fsm mode enabled as part
of the initialization. The sequence seems to be the same as used
by clk-pll, so move the function which does this into a common
place and reuse it for the clk-alpha-pll

Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
Signed-off-by: Taniya Das <tdas@codeaurora.org>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
2016-11-01 18:39:17 -07:00

479 lines
12 KiB
C

/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/clk-provider.h>
#include <linux/regmap.h>
#include <linux/delay.h>
#include "clk-alpha-pll.h"
#include "common.h"
#define PLL_MODE 0x00
# define PLL_OUTCTRL BIT(0)
# define PLL_BYPASSNL BIT(1)
# define PLL_RESET_N BIT(2)
# define PLL_OFFLINE_REQ BIT(7)
# define PLL_LOCK_COUNT_SHIFT 8
# define PLL_LOCK_COUNT_MASK 0x3f
# define PLL_BIAS_COUNT_SHIFT 14
# define PLL_BIAS_COUNT_MASK 0x3f
# define PLL_VOTE_FSM_ENA BIT(20)
# define PLL_FSM_ENA BIT(20)
# define PLL_VOTE_FSM_RESET BIT(21)
# define PLL_OFFLINE_ACK BIT(28)
# define PLL_ACTIVE_FLAG BIT(30)
# define PLL_LOCK_DET BIT(31)
#define PLL_L_VAL 0x04
#define PLL_ALPHA_VAL 0x08
#define PLL_ALPHA_VAL_U 0x0c
#define PLL_USER_CTL 0x10
# define PLL_POST_DIV_SHIFT 8
# define PLL_POST_DIV_MASK 0xf
# define PLL_ALPHA_EN BIT(24)
# define PLL_VCO_SHIFT 20
# define PLL_VCO_MASK 0x3
#define PLL_USER_CTL_U 0x14
#define PLL_CONFIG_CTL 0x18
#define PLL_CONFIG_CTL_U 0x20
#define PLL_TEST_CTL 0x1c
#define PLL_TEST_CTL_U 0x20
#define PLL_STATUS 0x24
/*
* Even though 40 bits are present, use only 32 for ease of calculation.
*/
#define ALPHA_REG_BITWIDTH 40
#define ALPHA_BITWIDTH 32
#define ALPHA_16BIT_MASK 0xffff
#define to_clk_alpha_pll(_hw) container_of(to_clk_regmap(_hw), \
struct clk_alpha_pll, clkr)
#define to_clk_alpha_pll_postdiv(_hw) container_of(to_clk_regmap(_hw), \
struct clk_alpha_pll_postdiv, clkr)
static int wait_for_pll(struct clk_alpha_pll *pll, u32 mask, bool inverse,
const char *action)
{
u32 val, off;
int count;
int ret;
const char *name = clk_hw_get_name(&pll->clkr.hw);
off = pll->offset;
ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
if (ret)
return ret;
for (count = 100; count > 0; count--) {
ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
if (ret)
return ret;
if (inverse && !(val & mask))
return 0;
else if ((val & mask) == mask)
return 0;
udelay(1);
}
WARN(1, "%s failed to %s!\n", name, action);
return -ETIMEDOUT;
}
#define wait_for_pll_enable_active(pll) \
wait_for_pll(pll, PLL_ACTIVE_FLAG, 0, "enable")
#define wait_for_pll_enable_lock(pll) \
wait_for_pll(pll, PLL_LOCK_DET, 0, "enable")
#define wait_for_pll_disable(pll) \
wait_for_pll(pll, PLL_ACTIVE_FLAG, 1, "disable")
#define wait_for_pll_offline(pll) \
wait_for_pll(pll, PLL_OFFLINE_ACK, 0, "offline")
void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap,
const struct alpha_pll_config *config)
{
u32 val, mask;
u32 off = pll->offset;
regmap_write(regmap, off + PLL_L_VAL, config->l);
regmap_write(regmap, off + PLL_ALPHA_VAL, config->alpha);
regmap_write(regmap, off + PLL_CONFIG_CTL, config->config_ctl_val);
regmap_write(regmap, off + PLL_CONFIG_CTL_U, config->config_ctl_hi_val);
val = config->main_output_mask;
val |= config->aux_output_mask;
val |= config->aux2_output_mask;
val |= config->early_output_mask;
val |= config->pre_div_val;
val |= config->post_div_val;
val |= config->vco_val;
mask = config->main_output_mask;
mask |= config->aux_output_mask;
mask |= config->aux2_output_mask;
mask |= config->early_output_mask;
mask |= config->pre_div_mask;
mask |= config->post_div_mask;
mask |= config->vco_mask;
regmap_update_bits(regmap, off + PLL_USER_CTL, mask, val);
if (pll->flags & SUPPORTS_FSM_MODE)
qcom_pll_set_fsm_mode(regmap, off + PLL_MODE, 6, 0);
}
static int clk_alpha_pll_hwfsm_enable(struct clk_hw *hw)
{
int ret;
u32 val, off;
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
off = pll->offset;
ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
if (ret)
return ret;
val |= PLL_FSM_ENA;
if (pll->flags & SUPPORTS_OFFLINE_REQ)
val &= ~PLL_OFFLINE_REQ;
ret = regmap_write(pll->clkr.regmap, off + PLL_MODE, val);
if (ret)
return ret;
/* Make sure enable request goes through before waiting for update */
mb();
return wait_for_pll_enable_active(pll);
}
static void clk_alpha_pll_hwfsm_disable(struct clk_hw *hw)
{
int ret;
u32 val, off;
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
off = pll->offset;
ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
if (ret)
return;
if (pll->flags & SUPPORTS_OFFLINE_REQ) {
ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
PLL_OFFLINE_REQ, PLL_OFFLINE_REQ);
if (ret)
return;
ret = wait_for_pll_offline(pll);
if (ret)
return;
}
/* Disable hwfsm */
ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
PLL_FSM_ENA, 0);
if (ret)
return;
wait_for_pll_disable(pll);
}
static int clk_alpha_pll_enable(struct clk_hw *hw)
{
int ret;
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
u32 val, mask, off;
off = pll->offset;
mask = PLL_OUTCTRL | PLL_RESET_N | PLL_BYPASSNL;
ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
if (ret)
return ret;
/* If in FSM mode, just vote for it */
if (val & PLL_VOTE_FSM_ENA) {
ret = clk_enable_regmap(hw);
if (ret)
return ret;
return wait_for_pll_enable_active(pll);
}
/* Skip if already enabled */
if ((val & mask) == mask)
return 0;
ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
PLL_BYPASSNL, PLL_BYPASSNL);
if (ret)
return ret;
/*
* H/W requires a 5us delay between disabling the bypass and
* de-asserting the reset.
*/
mb();
udelay(5);
ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
PLL_RESET_N, PLL_RESET_N);
if (ret)
return ret;
ret = wait_for_pll_enable_lock(pll);
if (ret)
return ret;
ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
PLL_OUTCTRL, PLL_OUTCTRL);
/* Ensure that the write above goes through before returning. */
mb();
return ret;
}
static void clk_alpha_pll_disable(struct clk_hw *hw)
{
int ret;
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
u32 val, mask, off;
off = pll->offset;
ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
if (ret)
return;
/* If in FSM mode, just unvote it */
if (val & PLL_VOTE_FSM_ENA) {
clk_disable_regmap(hw);
return;
}
mask = PLL_OUTCTRL;
regmap_update_bits(pll->clkr.regmap, off + PLL_MODE, mask, 0);
/* Delay of 2 output clock ticks required until output is disabled */
mb();
udelay(1);
mask = PLL_RESET_N | PLL_BYPASSNL;
regmap_update_bits(pll->clkr.regmap, off + PLL_MODE, mask, 0);
}
static unsigned long alpha_pll_calc_rate(u64 prate, u32 l, u32 a)
{
return (prate * l) + ((prate * a) >> ALPHA_BITWIDTH);
}
static unsigned long
alpha_pll_round_rate(unsigned long rate, unsigned long prate, u32 *l, u64 *a)
{
u64 remainder;
u64 quotient;
quotient = rate;
remainder = do_div(quotient, prate);
*l = quotient;
if (!remainder) {
*a = 0;
return rate;
}
/* Upper ALPHA_BITWIDTH bits of Alpha */
quotient = remainder << ALPHA_BITWIDTH;
remainder = do_div(quotient, prate);
if (remainder)
quotient++;
*a = quotient;
return alpha_pll_calc_rate(prate, *l, *a);
}
static const struct pll_vco *
alpha_pll_find_vco(const struct clk_alpha_pll *pll, unsigned long rate)
{
const struct pll_vco *v = pll->vco_table;
const struct pll_vco *end = v + pll->num_vco;
for (; v < end; v++)
if (rate >= v->min_freq && rate <= v->max_freq)
return v;
return NULL;
}
static unsigned long
clk_alpha_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
u32 l, low, high, ctl;
u64 a = 0, prate = parent_rate;
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
u32 off = pll->offset;
regmap_read(pll->clkr.regmap, off + PLL_L_VAL, &l);
regmap_read(pll->clkr.regmap, off + PLL_USER_CTL, &ctl);
if (ctl & PLL_ALPHA_EN) {
regmap_read(pll->clkr.regmap, off + PLL_ALPHA_VAL, &low);
if (pll->flags & SUPPORTS_16BIT_ALPHA) {
a = low & ALPHA_16BIT_MASK;
} else {
regmap_read(pll->clkr.regmap, off + PLL_ALPHA_VAL_U,
&high);
a = (u64)high << 32 | low;
a >>= ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH;
}
}
return alpha_pll_calc_rate(prate, l, a);
}
static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long prate)
{
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
const struct pll_vco *vco;
u32 l, off = pll->offset;
u64 a;
rate = alpha_pll_round_rate(rate, prate, &l, &a);
vco = alpha_pll_find_vco(pll, rate);
if (!vco) {
pr_err("alpha pll not in a valid vco range\n");
return -EINVAL;
}
regmap_write(pll->clkr.regmap, off + PLL_L_VAL, l);
if (pll->flags & SUPPORTS_16BIT_ALPHA) {
regmap_write(pll->clkr.regmap, off + PLL_ALPHA_VAL,
a & ALPHA_16BIT_MASK);
} else {
a <<= (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH);
regmap_write(pll->clkr.regmap, off + PLL_ALPHA_VAL_U, a >> 32);
}
regmap_update_bits(pll->clkr.regmap, off + PLL_USER_CTL,
PLL_VCO_MASK << PLL_VCO_SHIFT,
vco->val << PLL_VCO_SHIFT);
regmap_update_bits(pll->clkr.regmap, off + PLL_USER_CTL, PLL_ALPHA_EN,
PLL_ALPHA_EN);
return 0;
}
static long clk_alpha_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
u32 l;
u64 a;
unsigned long min_freq, max_freq;
rate = alpha_pll_round_rate(rate, *prate, &l, &a);
if (alpha_pll_find_vco(pll, rate))
return rate;
min_freq = pll->vco_table[0].min_freq;
max_freq = pll->vco_table[pll->num_vco - 1].max_freq;
return clamp(rate, min_freq, max_freq);
}
const struct clk_ops clk_alpha_pll_ops = {
.enable = clk_alpha_pll_enable,
.disable = clk_alpha_pll_disable,
.recalc_rate = clk_alpha_pll_recalc_rate,
.round_rate = clk_alpha_pll_round_rate,
.set_rate = clk_alpha_pll_set_rate,
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_ops);
const struct clk_ops clk_alpha_pll_hwfsm_ops = {
.enable = clk_alpha_pll_hwfsm_enable,
.disable = clk_alpha_pll_hwfsm_disable,
.recalc_rate = clk_alpha_pll_recalc_rate,
.round_rate = clk_alpha_pll_round_rate,
.set_rate = clk_alpha_pll_set_rate,
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_hwfsm_ops);
static unsigned long
clk_alpha_pll_postdiv_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw);
u32 ctl;
regmap_read(pll->clkr.regmap, pll->offset + PLL_USER_CTL, &ctl);
ctl >>= PLL_POST_DIV_SHIFT;
ctl &= PLL_POST_DIV_MASK;
return parent_rate >> fls(ctl);
}
static const struct clk_div_table clk_alpha_div_table[] = {
{ 0x0, 1 },
{ 0x1, 2 },
{ 0x3, 4 },
{ 0x7, 8 },
{ 0xf, 16 },
{ }
};
static long
clk_alpha_pll_postdiv_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw);
return divider_round_rate(hw, rate, prate, clk_alpha_div_table,
pll->width, CLK_DIVIDER_POWER_OF_TWO);
}
static int clk_alpha_pll_postdiv_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw);
int div;
/* 16 -> 0xf, 8 -> 0x7, 4 -> 0x3, 2 -> 0x1, 1 -> 0x0 */
div = DIV_ROUND_UP_ULL((u64)parent_rate, rate) - 1;
return regmap_update_bits(pll->clkr.regmap, pll->offset + PLL_USER_CTL,
PLL_POST_DIV_MASK << PLL_POST_DIV_SHIFT,
div << PLL_POST_DIV_SHIFT);
}
const struct clk_ops clk_alpha_pll_postdiv_ops = {
.recalc_rate = clk_alpha_pll_postdiv_recalc_rate,
.round_rate = clk_alpha_pll_postdiv_round_rate,
.set_rate = clk_alpha_pll_postdiv_set_rate,
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_postdiv_ops);