linux/drivers/clk/rockchip/clk.c
Heiko Stübner b2155a71a7 clk: rockchip: implement the fraction divider branch type
Rockchip SoCs may provide fraction dividers for some clocks, mostly for
i2s and uarts. In contrast to the other registers, these do not use
the hiword-mask paradigm, but instead split the register into the upper
16 bit for the nominator and the lower 16 bit for the denominator.

The common clock framework got a generic fractional divider clock type
recently that can accomodate this setting easily. All currently known
fraction dividers have a separate gate too, therefore implement the
divider as composite using the ops-struct from fractional_divider clock
and add the gate if necessary.

Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Signed-off-by: Mike Turquette <mturquette@linaro.org>
2014-09-02 15:03:17 -07:00

299 lines
7.8 KiB
C

/*
* Copyright (c) 2014 MundoReader S.L.
* Author: Heiko Stuebner <heiko@sntech.de>
*
* based on
*
* samsung/clk.c
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* Copyright (c) 2013 Linaro Ltd.
* Author: Thomas Abraham <thomas.ab@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/slab.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include "clk.h"
/**
* Register a clock branch.
* Most clock branches have a form like
*
* src1 --|--\
* |M |--[GATE]-[DIV]-
* src2 --|--/
*
* sometimes without one of those components.
*/
struct clk *rockchip_clk_register_branch(const char *name,
const char **parent_names, u8 num_parents, void __iomem *base,
int muxdiv_offset, u8 mux_shift, u8 mux_width, u8 mux_flags,
u8 div_shift, u8 div_width, u8 div_flags,
struct clk_div_table *div_table, int gate_offset,
u8 gate_shift, u8 gate_flags, unsigned long flags,
spinlock_t *lock)
{
struct clk *clk;
struct clk_mux *mux = NULL;
struct clk_gate *gate = NULL;
struct clk_divider *div = NULL;
const struct clk_ops *mux_ops = NULL, *div_ops = NULL,
*gate_ops = NULL;
if (num_parents > 1) {
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
return ERR_PTR(-ENOMEM);
mux->reg = base + muxdiv_offset;
mux->shift = mux_shift;
mux->mask = BIT(mux_width) - 1;
mux->flags = mux_flags;
mux->lock = lock;
mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops
: &clk_mux_ops;
}
if (gate_offset >= 0) {
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
return ERR_PTR(-ENOMEM);
gate->flags = gate_flags;
gate->reg = base + gate_offset;
gate->bit_idx = gate_shift;
gate->lock = lock;
gate_ops = &clk_gate_ops;
}
if (div_width > 0) {
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
return ERR_PTR(-ENOMEM);
div->flags = div_flags;
div->reg = base + muxdiv_offset;
div->shift = div_shift;
div->width = div_width;
div->lock = lock;
div->table = div_table;
div_ops = (div_flags & CLK_DIVIDER_READ_ONLY)
? &clk_divider_ro_ops
: &clk_divider_ops;
}
clk = clk_register_composite(NULL, name, parent_names, num_parents,
mux ? &mux->hw : NULL, mux_ops,
div ? &div->hw : NULL, div_ops,
gate ? &gate->hw : NULL, gate_ops,
flags);
return clk;
}
static struct clk *rockchip_clk_register_frac_branch(const char *name,
const char **parent_names, u8 num_parents, void __iomem *base,
int muxdiv_offset, u8 div_flags,
int gate_offset, u8 gate_shift, u8 gate_flags,
unsigned long flags, spinlock_t *lock)
{
struct clk *clk;
struct clk_gate *gate = NULL;
struct clk_fractional_divider *div = NULL;
const struct clk_ops *div_ops = NULL, *gate_ops = NULL;
if (gate_offset >= 0) {
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
return ERR_PTR(-ENOMEM);
gate->flags = gate_flags;
gate->reg = base + gate_offset;
gate->bit_idx = gate_shift;
gate->lock = lock;
gate_ops = &clk_gate_ops;
}
if (muxdiv_offset < 0)
return ERR_PTR(-EINVAL);
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
return ERR_PTR(-ENOMEM);
div->flags = div_flags;
div->reg = base + muxdiv_offset;
div->mshift = 16;
div->mmask = 0xffff0000;
div->nshift = 0;
div->nmask = 0xffff;
div->lock = lock;
div_ops = &clk_fractional_divider_ops;
clk = clk_register_composite(NULL, name, parent_names, num_parents,
NULL, NULL,
&div->hw, div_ops,
gate ? &gate->hw : NULL, gate_ops,
flags);
return clk;
}
static DEFINE_SPINLOCK(clk_lock);
static struct clk **clk_table;
static void __iomem *reg_base;
static struct clk_onecell_data clk_data;
static struct device_node *cru_node;
static struct regmap *grf;
void __init rockchip_clk_init(struct device_node *np, void __iomem *base,
unsigned long nr_clks)
{
reg_base = base;
cru_node = np;
grf = ERR_PTR(-EPROBE_DEFER);
clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
if (!clk_table)
pr_err("%s: could not allocate clock lookup table\n", __func__);
clk_data.clks = clk_table;
clk_data.clk_num = nr_clks;
of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
}
struct regmap *rockchip_clk_get_grf(void)
{
if (IS_ERR(grf))
grf = syscon_regmap_lookup_by_phandle(cru_node, "rockchip,grf");
return grf;
}
void rockchip_clk_add_lookup(struct clk *clk, unsigned int id)
{
if (clk_table && id)
clk_table[id] = clk;
}
void __init rockchip_clk_register_plls(struct rockchip_pll_clock *list,
unsigned int nr_pll, int grf_lock_offset)
{
struct clk *clk;
int idx;
for (idx = 0; idx < nr_pll; idx++, list++) {
clk = rockchip_clk_register_pll(list->type, list->name,
list->parent_names, list->num_parents,
reg_base, list->con_offset, grf_lock_offset,
list->lock_shift, list->mode_offset,
list->mode_shift, list->rate_table, &clk_lock);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s\n", __func__,
list->name);
continue;
}
rockchip_clk_add_lookup(clk, list->id);
}
}
void __init rockchip_clk_register_branches(
struct rockchip_clk_branch *list,
unsigned int nr_clk)
{
struct clk *clk = NULL;
unsigned int idx;
unsigned long flags;
for (idx = 0; idx < nr_clk; idx++, list++) {
flags = list->flags;
/* catch simple muxes */
switch (list->branch_type) {
case branch_mux:
clk = clk_register_mux(NULL, list->name,
list->parent_names, list->num_parents,
flags, reg_base + list->muxdiv_offset,
list->mux_shift, list->mux_width,
list->mux_flags, &clk_lock);
break;
case branch_divider:
if (list->div_table)
clk = clk_register_divider_table(NULL,
list->name, list->parent_names[0],
flags, reg_base + list->muxdiv_offset,
list->div_shift, list->div_width,
list->div_flags, list->div_table,
&clk_lock);
else
clk = clk_register_divider(NULL, list->name,
list->parent_names[0], flags,
reg_base + list->muxdiv_offset,
list->div_shift, list->div_width,
list->div_flags, &clk_lock);
break;
case branch_fraction_divider:
/* keep all gates untouched for now */
flags |= CLK_IGNORE_UNUSED;
clk = rockchip_clk_register_frac_branch(list->name,
list->parent_names, list->num_parents,
reg_base, list->muxdiv_offset, list->div_flags,
list->gate_offset, list->gate_shift,
list->gate_flags, flags, &clk_lock);
break;
case branch_gate:
flags |= CLK_SET_RATE_PARENT;
/* keep all gates untouched for now */
flags |= CLK_IGNORE_UNUSED;
clk = clk_register_gate(NULL, list->name,
list->parent_names[0], flags,
reg_base + list->gate_offset,
list->gate_shift, list->gate_flags, &clk_lock);
break;
case branch_composite:
/* keep all gates untouched for now */
flags |= CLK_IGNORE_UNUSED;
clk = rockchip_clk_register_branch(list->name,
list->parent_names, list->num_parents,
reg_base, list->muxdiv_offset, list->mux_shift,
list->mux_width, list->mux_flags,
list->div_shift, list->div_width,
list->div_flags, list->div_table,
list->gate_offset, list->gate_shift,
list->gate_flags, flags, &clk_lock);
break;
}
/* none of the cases above matched */
if (!clk) {
pr_err("%s: unknown clock type %d\n",
__func__, list->branch_type);
continue;
}
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s: %ld\n",
__func__, list->name, PTR_ERR(clk));
continue;
}
rockchip_clk_add_lookup(clk, list->id);
}
}