blob: b1212189bce416e3368b074db84f1b15a99a5c6c [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018 Synaptics Incorporated
* Copyright (c) 2015 Marvell Technology Group Ltd.
*
* Author: Jisheng Zhang <jszhang@kernel.org>
*
*/
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.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
#define CLK_SOURCE_MAX 6
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 + 1;
}
return 0;
}
static int berlin_clk_enable(struct clk_hw *hw)
{
u32 val;
struct berlin_clk *clk = to_berlin_clk(hw);
val = readl_relaxed(clk->base);
val |= CLKEN;
writel_relaxed(val, clk->base);
return 0;
}
static void berlin_clk_disable(struct clk_hw *hw)
{
u32 val;
struct berlin_clk *clk = to_berlin_clk(hw);
val = readl_relaxed(clk->base);
val &= ~CLKEN;
writel_relaxed(val, clk->base);
}
static int berlin_clk_is_enabled(struct clk_hw *hw)
{
u32 val;
struct berlin_clk *clk = to_berlin_clk(hw);
val = readl_relaxed(clk->base);
val &= CLKEN;
return val ? 1 : 0;
}
static const struct clk_ops berlin_clk_ops = {
.recalc_rate = berlin_clk_recalc_rate,
.get_parent = berlin_clk_get_parent,
.enable = berlin_clk_enable,
.disable = berlin_clk_disable,
.is_enabled = berlin_clk_is_enabled,
};
static struct clk * __init
berlin_clk_register(const char *name, int num_parents,
const char **parent_names, unsigned long flags,
void __iomem *base)
{
struct clk *clk;
struct berlin_clk *bclk;
struct clk_init_data init;
bclk = kzalloc(sizeof(*bclk), GFP_KERNEL);
if (!bclk)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &berlin_clk_ops;
init.parent_names = parent_names;
init.num_parents = num_parents;
init.flags = flags;
bclk->base = base;
bclk->hw.init = &init;
clk = clk_register(NULL, &bclk->hw);
if (IS_ERR(clk))
kfree(bclk);
return clk;
}
void __init berlin_clk_setup(struct device_node *np,
const struct clk_desc *descs,
struct clk_onecell_data *clk_data,
int n)
{
int i, ret, num_parents;
void __iomem *base;
struct clk **clks;
const char *parent_names[CLK_SOURCE_MAX];
num_parents = of_clk_get_parent_count(np);
if (num_parents <= 0 || num_parents > CLK_SOURCE_MAX)
return;
of_clk_parent_fill(np, parent_names, num_parents);
clks = kcalloc(n, sizeof(struct clk *), GFP_KERNEL);
if (!clks)
return;
base = of_iomap(np, 0);
if (WARN_ON(!base))
goto err_iomap;
for (i = 0; i < n; i++) {
struct clk *clk;
clk = berlin_clk_register(descs[i].name,
num_parents, parent_names,
descs[i].flags,
base + descs[i].offset);
if (WARN_ON(IS_ERR(clks[i])))
goto err_clk_register;
clks[i] = clk;
}
clk_data->clks = clks;
clk_data->clk_num = i;
ret = of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
if (WARN_ON(ret))
goto err_clk_register;
return;
err_clk_register:
for (i = 0; i < n; i++)
clk_unregister(clks[i]);
iounmap(base);
err_iomap:
kfree(clks);
}