blob: 5796a51f71a78f8f65e4f017be13bd59f7f30fd7 [file] [log] [blame]
/*
* Copyright (c) 2013 Marvell Technology Group Ltd.
*
* Author: Jisheng Zhang <jszhang@marvell.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/>.
*/
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/clk-provider.h>
#include "clk.h"
#define CLKEN (1 << 0)
#define CLKPLLSEL_MASK 7
#define CLKPLLSEL_SHIFT 1
#define CLKPLLSWITCH (1 << 4)
#define CLKSWITCH (1 << 5)
#define CLKD3SWITCH (1 << 6)
#define CLKSEL_MASK 7
#define CLKSEL_SHIFT 7
struct berlin_clk {
struct clk_hw hw;
void __iomem *base;
};
#define to_berlin_clk(hw) container_of(hw, struct berlin_clk, hw)
static u8 clk_div[] = {1, 2, 4, 6, 8, 12, 1, 1};
static unsigned long berlin_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
u32 val, divider;
struct berlin_clk *clk = to_berlin_clk(hw);
val = readl_relaxed(clk->base);
if (val & CLKD3SWITCH)
divider = 3;
else {
if (val & CLKSWITCH) {
val >>= CLKSEL_SHIFT;
val &= CLKSEL_MASK;
divider = clk_div[val];
} else
divider = 1;
}
return parent_rate / divider;
}
static u8 berlin_clk_get_parent(struct clk_hw *hw)
{
u32 val;
struct berlin_clk *clk = to_berlin_clk(hw);
val = readl_relaxed(clk->base);
if (val & CLKPLLSWITCH) {
val >>= CLKPLLSEL_SHIFT;
val &= CLKPLLSEL_MASK;
return val;
}
return 0;
}
static const struct clk_ops berlin_clk_ops = {
.recalc_rate = berlin_clk_recalc_rate,
.get_parent = berlin_clk_get_parent,
};
static void __init berlin_clk_setup(struct device_node *np)
{
struct clk_init_data init;
struct berlin_clk *bclk;
struct clk *clk;
const char *parent_names[5];
int i, ret;
bclk = kzalloc(sizeof(*bclk), GFP_KERNEL);
if (WARN_ON(!bclk))
return;
bclk->base = of_iomap(np, 0);
if (WARN_ON(!bclk->base))
return;
init.name = np->name;
init.ops = &berlin_clk_ops;
for (i = 0; i < ARRAY_SIZE(parent_names); i++) {
parent_names[i] = of_clk_get_parent_name(np, i);
if (!parent_names[i])
break;
}
init.parent_names = parent_names;
init.num_parents = i;
bclk->hw.init = &init;
clk = clk_register(NULL, &bclk->hw);
if (WARN_ON(IS_ERR(clk)))
return;
ret = of_clk_add_provider(np, of_clk_src_simple_get, clk);
if (WARN_ON(ret))
return;
}
struct berlin_grpctl {
struct clk **clks;
void __iomem *switch_base;
void __iomem *select_base;
struct clk_onecell_data onecell_data;
};
struct berlin_grpclk {
struct clk_hw hw;
struct berlin_grpctl *ctl;
int switch_shift;
int select_shift;
};
#define to_berlin_grpclk(hw) container_of(hw, struct berlin_grpclk, hw)
static u8 berlin_grpclk_get_parent(struct clk_hw *hw)
{
u32 val;
struct berlin_grpclk *clk = to_berlin_grpclk(hw);
struct berlin_grpctl *ctl = clk->ctl;
void __iomem *base = ctl->switch_base;
base += clk->switch_shift / 32 * 4;
val = readl_relaxed(base);
if (val & (1 << clk->switch_shift)) {
base = ctl->select_base;
base += clk->select_shift / 32 * 4;
val = readl_relaxed(base);
val >>= clk->select_shift;
val &= CLKPLLSEL_MASK;
return val;
}
return 0;
}
static unsigned long berlin_grpclk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
u32 val, divider;
struct berlin_grpclk *clk = to_berlin_grpclk(hw);
struct berlin_grpctl *ctl = clk->ctl;
void __iomem *base = ctl->switch_base;
int shift;
shift = clk->switch_shift + 2;
base += shift / 32 * 4;
shift = shift - shift / 32 * 32;
val = readl_relaxed(base);
if (val & (1 << shift))
divider = 3;
else {
if (unlikely(base != ctl->switch_base))
val = readl_relaxed(ctl->switch_base);
shift = clk->switch_shift + 1;
shift = shift - shift / 32 * 32;
if (val & (1 << shift)) {
base = ctl->select_base;
shift = clk->select_shift + 3;
base += shift / 32 * 4;
shift = shift - shift / 32 * 32;
val = readl_relaxed(base);
val >>= shift;
val &= CLKSEL_MASK;
divider = clk_div[val];
} else
divider = 1;
}
return parent_rate / divider;
}
static const struct clk_ops berlin_grpclk_ops = {
.recalc_rate = berlin_grpclk_recalc_rate,
.get_parent = berlin_grpclk_get_parent,
};
static struct clk * __init berlin_grpclk_register(const char *name,
const char **parent_names, int num_parents,
int switch_shift, int select_shift,
struct berlin_grpctl *ctl)
{
struct berlin_grpclk *grpclk;
struct clk *clk;
struct clk_init_data init;
/* allocate the gate */
grpclk = kzalloc(sizeof(*grpclk), GFP_KERNEL);
if (!grpclk) {
pr_err("%s: could not allocate berlin grpclk\n", __func__);
return ERR_PTR(-ENOMEM);
}
init.name = name;
init.ops = &berlin_grpclk_ops;
init.parent_names = parent_names;
init.num_parents = num_parents;
grpclk->ctl = ctl;
grpclk->switch_shift = switch_shift;
grpclk->select_shift = select_shift;
grpclk->hw.init = &init;
clk = clk_register(NULL, &grpclk->hw);
if (IS_ERR(clk))
kfree(grpclk);
return clk;
}
void __init berlin_grpclk_setup(struct device_node *np,
const struct grpclk_desc *desc)
{
struct berlin_grpctl *ctl;
const char *parent_names[5];
int i, n, ret;
ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
if (WARN_ON(!ctl))
return;
ctl->switch_base = of_iomap(np, 0);
if (WARN_ON(!ctl->switch_base))
return;
ctl->select_base = of_iomap(np, 1);
if (WARN_ON(!ctl->select_base))
return;
/* Count, allocate, and register clock gates */
for (n = 0; desc[n].name;)
n++;
ctl->clks = kzalloc(n * sizeof(struct clk *), GFP_KERNEL);
if (WARN_ON(!ctl->clks))
return;
for (i = 0; i < ARRAY_SIZE(parent_names); i++)
parent_names[i] = of_clk_get_parent_name(np, i);
for (i = 0; i < n; i++) {
ctl->clks[i] = berlin_grpclk_register(desc[i].name,
parent_names, ARRAY_SIZE(parent_names),
desc[i].switch_shift, desc[i].select_shift,
ctl);
WARN_ON(IS_ERR(ctl->clks[i]));
}
ctl->onecell_data.clks = ctl->clks;
ctl->onecell_data.clk_num = i;
ret = of_clk_add_provider(np, of_clk_src_onecell_get,
&ctl->onecell_data);
if (WARN_ON(ret))
return;
}
static const __initconst struct of_device_id clk_match[] = {
{ .compatible = "fixed-clock", .data = of_fixed_clk_setup, },
{ .compatible = "fixed-factor-clock", .data = of_fixed_factor_clk_setup, },
{ .compatible = "marvell,berlin2q-pll", .data = berlin2q_pll_setup, },
{ .compatible = "marvell,berlin2q-avpll", .data = berlin2q_avpll_setup, },
{ .compatible = "marvell,berlin2cdp-pll", .data = berlin2cdp_pll_setup, },
{ .compatible = "marvell,berlin2cdp-avpll", .data = berlin2cdp_avpll_setup, },
{ .compatible = "marvell,berlin-clk", .data = berlin_clk_setup, },
{ .compatible = "marvell,berlin2q-grpclk", .data = berlin2q_grpclk_setup, },
{}
};
void __init berlin_clk_init(void)
{
of_clk_init(clk_match);
}