/* * 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, size_t 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); } }