250 lines
6.0 KiB
C
250 lines
6.0 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/>.
|
|
*/
|
|
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "clkc.h"
|
|
|
|
static DEFINE_SPINLOCK(clk_lock);
|
|
|
|
static struct clk **clks;
|
|
static struct clk_onecell_data clk_data;
|
|
|
|
struct clk ** __init meson_clk_init(struct device_node *np,
|
|
unsigned long nr_clks)
|
|
{
|
|
clks = kcalloc(nr_clks, sizeof(*clks), GFP_KERNEL);
|
|
if (!clks)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
clk_data.clks = clks;
|
|
clk_data.clk_num = nr_clks;
|
|
of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
|
|
|
|
return clks;
|
|
}
|
|
|
|
static void meson_clk_add_lookup(struct clk *clk, unsigned int id)
|
|
{
|
|
if (clks && id)
|
|
clks[id] = clk;
|
|
}
|
|
|
|
static struct clk * __init
|
|
meson_clk_register_composite(const struct clk_conf *clk_conf,
|
|
void __iomem *clk_base)
|
|
{
|
|
struct clk *clk;
|
|
struct clk_mux *mux = NULL;
|
|
struct clk_divider *div = NULL;
|
|
struct clk_gate *gate = NULL;
|
|
const struct clk_ops *mux_ops = NULL;
|
|
const struct composite_conf *composite_conf;
|
|
|
|
composite_conf = clk_conf->conf.composite;
|
|
|
|
if (clk_conf->num_parents > 1) {
|
|
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
|
|
if (!mux)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
mux->reg = clk_base + clk_conf->reg_off
|
|
+ composite_conf->mux_parm.reg_off;
|
|
mux->shift = composite_conf->mux_parm.shift;
|
|
mux->mask = BIT(composite_conf->mux_parm.width) - 1;
|
|
mux->flags = composite_conf->mux_flags;
|
|
mux->lock = &clk_lock;
|
|
mux->table = composite_conf->mux_table;
|
|
mux_ops = (composite_conf->mux_flags & CLK_MUX_READ_ONLY) ?
|
|
&clk_mux_ro_ops : &clk_mux_ops;
|
|
}
|
|
|
|
if (MESON_PARM_APPLICABLE(&composite_conf->div_parm)) {
|
|
div = kzalloc(sizeof(*div), GFP_KERNEL);
|
|
if (!div) {
|
|
clk = ERR_PTR(-ENOMEM);
|
|
goto error;
|
|
}
|
|
|
|
div->reg = clk_base + clk_conf->reg_off
|
|
+ composite_conf->div_parm.reg_off;
|
|
div->shift = composite_conf->div_parm.shift;
|
|
div->width = composite_conf->div_parm.width;
|
|
div->lock = &clk_lock;
|
|
div->flags = composite_conf->div_flags;
|
|
div->table = composite_conf->div_table;
|
|
}
|
|
|
|
if (MESON_PARM_APPLICABLE(&composite_conf->gate_parm)) {
|
|
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
|
|
if (!gate) {
|
|
clk = ERR_PTR(-ENOMEM);
|
|
goto error;
|
|
}
|
|
|
|
gate->reg = clk_base + clk_conf->reg_off
|
|
+ composite_conf->div_parm.reg_off;
|
|
gate->bit_idx = composite_conf->gate_parm.shift;
|
|
gate->flags = composite_conf->gate_flags;
|
|
gate->lock = &clk_lock;
|
|
}
|
|
|
|
clk = clk_register_composite(NULL, clk_conf->clk_name,
|
|
clk_conf->clks_parent,
|
|
clk_conf->num_parents,
|
|
mux ? &mux->hw : NULL, mux_ops,
|
|
div ? &div->hw : NULL, &clk_divider_ops,
|
|
gate ? &gate->hw : NULL, &clk_gate_ops,
|
|
clk_conf->flags);
|
|
if (IS_ERR(clk))
|
|
goto error;
|
|
|
|
return clk;
|
|
|
|
error:
|
|
kfree(gate);
|
|
kfree(div);
|
|
kfree(mux);
|
|
|
|
return clk;
|
|
}
|
|
|
|
static struct clk * __init
|
|
meson_clk_register_fixed_factor(const struct clk_conf *clk_conf,
|
|
void __iomem *clk_base)
|
|
{
|
|
struct clk *clk;
|
|
const struct fixed_fact_conf *fixed_fact_conf;
|
|
const struct parm *p;
|
|
unsigned int mult, div;
|
|
u32 reg;
|
|
|
|
fixed_fact_conf = &clk_conf->conf.fixed_fact;
|
|
|
|
mult = clk_conf->conf.fixed_fact.mult;
|
|
div = clk_conf->conf.fixed_fact.div;
|
|
|
|
if (!mult) {
|
|
mult = 1;
|
|
p = &fixed_fact_conf->mult_parm;
|
|
if (MESON_PARM_APPLICABLE(p)) {
|
|
reg = readl(clk_base + clk_conf->reg_off + p->reg_off);
|
|
mult = PARM_GET(p->width, p->shift, reg);
|
|
}
|
|
}
|
|
|
|
if (!div) {
|
|
div = 1;
|
|
p = &fixed_fact_conf->div_parm;
|
|
if (MESON_PARM_APPLICABLE(p)) {
|
|
reg = readl(clk_base + clk_conf->reg_off + p->reg_off);
|
|
mult = PARM_GET(p->width, p->shift, reg);
|
|
}
|
|
}
|
|
|
|
clk = clk_register_fixed_factor(NULL,
|
|
clk_conf->clk_name,
|
|
clk_conf->clks_parent[0],
|
|
clk_conf->flags,
|
|
mult, div);
|
|
|
|
return clk;
|
|
}
|
|
|
|
static struct clk * __init
|
|
meson_clk_register_fixed_rate(const struct clk_conf *clk_conf,
|
|
void __iomem *clk_base)
|
|
{
|
|
struct clk *clk;
|
|
const struct fixed_rate_conf *fixed_rate_conf;
|
|
const struct parm *r;
|
|
unsigned long rate;
|
|
u32 reg;
|
|
|
|
fixed_rate_conf = &clk_conf->conf.fixed_rate;
|
|
rate = fixed_rate_conf->rate;
|
|
|
|
if (!rate) {
|
|
r = &fixed_rate_conf->rate_parm;
|
|
reg = readl(clk_base + clk_conf->reg_off + r->reg_off);
|
|
rate = PARM_GET(r->width, r->shift, reg);
|
|
}
|
|
|
|
rate *= 1000000;
|
|
|
|
clk = clk_register_fixed_rate(NULL,
|
|
clk_conf->clk_name,
|
|
clk_conf->num_parents
|
|
? clk_conf->clks_parent[0] : NULL,
|
|
clk_conf->flags, rate);
|
|
|
|
return clk;
|
|
}
|
|
|
|
void __init meson_clk_register_clks(const struct clk_conf *clk_confs,
|
|
unsigned int nr_confs,
|
|
void __iomem *clk_base)
|
|
{
|
|
unsigned int i;
|
|
struct clk *clk = NULL;
|
|
|
|
for (i = 0; i < nr_confs; i++) {
|
|
const struct clk_conf *clk_conf = &clk_confs[i];
|
|
|
|
switch (clk_conf->clk_type) {
|
|
case CLK_FIXED_RATE:
|
|
clk = meson_clk_register_fixed_rate(clk_conf,
|
|
clk_base);
|
|
break;
|
|
case CLK_FIXED_FACTOR:
|
|
clk = meson_clk_register_fixed_factor(clk_conf,
|
|
clk_base);
|
|
break;
|
|
case CLK_COMPOSITE:
|
|
clk = meson_clk_register_composite(clk_conf,
|
|
clk_base);
|
|
break;
|
|
case CLK_CPU:
|
|
clk = meson_clk_register_cpu(clk_conf, clk_base,
|
|
&clk_lock);
|
|
break;
|
|
case CLK_PLL:
|
|
clk = meson_clk_register_pll(clk_conf, clk_base,
|
|
&clk_lock);
|
|
break;
|
|
default:
|
|
clk = NULL;
|
|
}
|
|
|
|
if (!clk) {
|
|
pr_err("%s: unknown clock type %d\n", __func__,
|
|
clk_conf->clk_type);
|
|
continue;
|
|
}
|
|
|
|
if (IS_ERR(clk)) {
|
|
pr_warn("%s: Unable to create %s clock\n", __func__,
|
|
clk_conf->clk_name);
|
|
continue;
|
|
}
|
|
|
|
meson_clk_add_lookup(clk, clk_conf->clk_id);
|
|
}
|
|
}
|