forked from Minki/linux
45fcbec70c
In recent Amlogic GXBB, GXL and GXM SoCs, the GP0 PLL needs some specific parameters in order to initialize and lock correctly. This patch adds an optional PARAM table used to initialize the PLL to a default value with it's parameters in order to achieve to desired frequency. The GP0 PLL in GXBB, GXL/GXM also needs some tweaks in the initialization steps, and these are exposed along the PARAM table. Signed-off-by: Neil Armstrong <narmstrong@baylibre.com> Signed-off-by: Michael Turquette <mturquette@baylibre.com> Link: lkml.kernel.org/r/1490178747-14837-2-git-send-email-narmstrong@baylibre.com
255 lines
6.5 KiB
C
255 lines
6.5 KiB
C
/*
|
|
* Copyright (c) 2015 Endless Mobile, Inc.
|
|
* Author: Carlo Caione <carlo@endlessm.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* In the most basic form, a Meson PLL is composed as follows:
|
|
*
|
|
* PLL
|
|
* +------------------------------+
|
|
* | |
|
|
* in -----[ /N ]---[ *M ]---[ >>OD ]----->> out
|
|
* | ^ ^ |
|
|
* +------------------------------+
|
|
* | |
|
|
* FREF VCO
|
|
*
|
|
* out = (in * M / N) >> OD
|
|
*/
|
|
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
|
|
#include "clkc.h"
|
|
|
|
#define MESON_PLL_RESET BIT(29)
|
|
#define MESON_PLL_LOCK BIT(31)
|
|
|
|
#define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw)
|
|
|
|
static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct meson_clk_pll *pll = to_meson_clk_pll(hw);
|
|
struct parm *p;
|
|
unsigned long parent_rate_mhz = parent_rate / 1000000;
|
|
unsigned long rate_mhz;
|
|
u16 n, m, frac = 0, od, od2 = 0;
|
|
u32 reg;
|
|
|
|
p = &pll->n;
|
|
reg = readl(pll->base + p->reg_off);
|
|
n = PARM_GET(p->width, p->shift, reg);
|
|
|
|
p = &pll->m;
|
|
reg = readl(pll->base + p->reg_off);
|
|
m = PARM_GET(p->width, p->shift, reg);
|
|
|
|
p = &pll->od;
|
|
reg = readl(pll->base + p->reg_off);
|
|
od = PARM_GET(p->width, p->shift, reg);
|
|
|
|
p = &pll->od2;
|
|
if (p->width) {
|
|
reg = readl(pll->base + p->reg_off);
|
|
od2 = PARM_GET(p->width, p->shift, reg);
|
|
}
|
|
|
|
p = &pll->frac;
|
|
if (p->width) {
|
|
reg = readl(pll->base + p->reg_off);
|
|
frac = PARM_GET(p->width, p->shift, reg);
|
|
rate_mhz = (parent_rate_mhz * m + \
|
|
(parent_rate_mhz * frac >> 12)) * 2 / n;
|
|
rate_mhz = rate_mhz >> od >> od2;
|
|
} else
|
|
rate_mhz = (parent_rate_mhz * m / n) >> od >> od2;
|
|
|
|
return rate_mhz * 1000000;
|
|
}
|
|
|
|
static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
struct meson_clk_pll *pll = to_meson_clk_pll(hw);
|
|
const struct pll_rate_table *rate_table = pll->rate_table;
|
|
int i;
|
|
|
|
for (i = 0; i < pll->rate_count; i++) {
|
|
if (rate <= rate_table[i].rate)
|
|
return rate_table[i].rate;
|
|
}
|
|
|
|
/* else return the smallest value */
|
|
return rate_table[0].rate;
|
|
}
|
|
|
|
static const struct pll_rate_table *meson_clk_get_pll_settings(struct meson_clk_pll *pll,
|
|
unsigned long rate)
|
|
{
|
|
const struct pll_rate_table *rate_table = pll->rate_table;
|
|
int i;
|
|
|
|
for (i = 0; i < pll->rate_count; i++) {
|
|
if (rate == rate_table[i].rate)
|
|
return &rate_table[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Specific wait loop for GXL/GXM GP0 PLL */
|
|
static int meson_clk_pll_wait_lock_reset(struct meson_clk_pll *pll,
|
|
struct parm *p_n)
|
|
{
|
|
int delay = 100;
|
|
u32 reg;
|
|
|
|
while (delay > 0) {
|
|
reg = readl(pll->base + p_n->reg_off);
|
|
writel(reg | MESON_PLL_RESET, pll->base + p_n->reg_off);
|
|
udelay(10);
|
|
writel(reg & ~MESON_PLL_RESET, pll->base + p_n->reg_off);
|
|
|
|
/* This delay comes from AMLogic tree clk-gp0-gxl driver */
|
|
mdelay(1);
|
|
|
|
reg = readl(pll->base + p_n->reg_off);
|
|
if (reg & MESON_PLL_LOCK)
|
|
return 0;
|
|
delay--;
|
|
}
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int meson_clk_pll_wait_lock(struct meson_clk_pll *pll,
|
|
struct parm *p_n)
|
|
{
|
|
int delay = 24000000;
|
|
u32 reg;
|
|
|
|
while (delay > 0) {
|
|
reg = readl(pll->base + p_n->reg_off);
|
|
|
|
if (reg & MESON_PLL_LOCK)
|
|
return 0;
|
|
delay--;
|
|
}
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void meson_clk_pll_init_params(struct meson_clk_pll *pll)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0 ; i < pll->params.params_count ; ++i)
|
|
writel(pll->params.params_table[i].value,
|
|
pll->base + pll->params.params_table[i].reg_off);
|
|
}
|
|
|
|
static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct meson_clk_pll *pll = to_meson_clk_pll(hw);
|
|
struct parm *p;
|
|
const struct pll_rate_table *rate_set;
|
|
unsigned long old_rate;
|
|
int ret = 0;
|
|
u32 reg;
|
|
|
|
if (parent_rate == 0 || rate == 0)
|
|
return -EINVAL;
|
|
|
|
old_rate = rate;
|
|
|
|
rate_set = meson_clk_get_pll_settings(pll, rate);
|
|
if (!rate_set)
|
|
return -EINVAL;
|
|
|
|
/* Initialize the PLL in a clean state if specified */
|
|
if (pll->params.params_count)
|
|
meson_clk_pll_init_params(pll);
|
|
|
|
/* PLL reset */
|
|
p = &pll->n;
|
|
reg = readl(pll->base + p->reg_off);
|
|
/* If no_init_reset is provided, avoid resetting at this point */
|
|
if (!pll->params.no_init_reset)
|
|
writel(reg | MESON_PLL_RESET, pll->base + p->reg_off);
|
|
|
|
reg = PARM_SET(p->width, p->shift, reg, rate_set->n);
|
|
writel(reg, pll->base + p->reg_off);
|
|
|
|
p = &pll->m;
|
|
reg = readl(pll->base + p->reg_off);
|
|
reg = PARM_SET(p->width, p->shift, reg, rate_set->m);
|
|
writel(reg, pll->base + p->reg_off);
|
|
|
|
p = &pll->od;
|
|
reg = readl(pll->base + p->reg_off);
|
|
reg = PARM_SET(p->width, p->shift, reg, rate_set->od);
|
|
writel(reg, pll->base + p->reg_off);
|
|
|
|
p = &pll->od2;
|
|
if (p->width) {
|
|
reg = readl(pll->base + p->reg_off);
|
|
reg = PARM_SET(p->width, p->shift, reg, rate_set->od2);
|
|
writel(reg, pll->base + p->reg_off);
|
|
}
|
|
|
|
p = &pll->frac;
|
|
if (p->width) {
|
|
reg = readl(pll->base + p->reg_off);
|
|
reg = PARM_SET(p->width, p->shift, reg, rate_set->frac);
|
|
writel(reg, pll->base + p->reg_off);
|
|
}
|
|
|
|
p = &pll->n;
|
|
/* If clear_reset_for_lock is provided, remove the reset bit here */
|
|
if (pll->params.clear_reset_for_lock) {
|
|
reg = readl(pll->base + p->reg_off);
|
|
writel(reg & ~MESON_PLL_RESET, pll->base + p->reg_off);
|
|
}
|
|
|
|
/* If reset_lock_loop, use a special loop including resetting */
|
|
if (pll->params.reset_lock_loop)
|
|
ret = meson_clk_pll_wait_lock_reset(pll, p);
|
|
else
|
|
ret = meson_clk_pll_wait_lock(pll, p);
|
|
if (ret) {
|
|
pr_warn("%s: pll did not lock, trying to restore old rate %lu\n",
|
|
__func__, old_rate);
|
|
meson_clk_pll_set_rate(hw, old_rate, parent_rate);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
const struct clk_ops meson_clk_pll_ops = {
|
|
.recalc_rate = meson_clk_pll_recalc_rate,
|
|
.round_rate = meson_clk_pll_round_rate,
|
|
.set_rate = meson_clk_pll_set_rate,
|
|
};
|
|
|
|
const struct clk_ops meson_clk_pll_ro_ops = {
|
|
.recalc_rate = meson_clk_pll_recalc_rate,
|
|
};
|