| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954 |
- From f851b4ea6cae9fd5875036b6d3968375882ce56b Mon Sep 17 00:00:00 2001
- From: James Liao <[email protected]>
- Date: Thu, 23 Apr 2015 10:35:39 +0200
- Subject: [PATCH 02/76] clk: mediatek: Add initial common clock support for
- Mediatek SoCs.
- This patch adds common clock support for Mediatek SoCs, including plls,
- muxes and clock gates.
- Signed-off-by: James Liao <[email protected]>
- Signed-off-by: Henry Chen <[email protected]>
- Signed-off-by: Sascha Hauer <[email protected]>
- ---
- drivers/clk/Makefile | 1 +
- drivers/clk/mediatek/Makefile | 1 +
- drivers/clk/mediatek/clk-gate.c | 137 ++++++++++++++++
- drivers/clk/mediatek/clk-gate.h | 49 ++++++
- drivers/clk/mediatek/clk-mtk.c | 220 ++++++++++++++++++++++++++
- drivers/clk/mediatek/clk-mtk.h | 159 +++++++++++++++++++
- drivers/clk/mediatek/clk-pll.c | 332 +++++++++++++++++++++++++++++++++++++++
- 7 files changed, 899 insertions(+)
- create mode 100644 drivers/clk/mediatek/Makefile
- create mode 100644 drivers/clk/mediatek/clk-gate.c
- create mode 100644 drivers/clk/mediatek/clk-gate.h
- create mode 100644 drivers/clk/mediatek/clk-mtk.c
- create mode 100644 drivers/clk/mediatek/clk-mtk.h
- create mode 100644 drivers/clk/mediatek/clk-pll.c
- --- a/drivers/clk/Makefile
- +++ b/drivers/clk/Makefile
- @@ -51,6 +51,7 @@ obj-$(CONFIG_ARCH_HI3xxx) += hisilicon/
- obj-$(CONFIG_ARCH_HIP04) += hisilicon/
- obj-$(CONFIG_ARCH_HIX5HD2) += hisilicon/
- obj-$(CONFIG_COMMON_CLK_KEYSTONE) += keystone/
- +obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
- ifeq ($(CONFIG_COMMON_CLK), y)
- obj-$(CONFIG_ARCH_MMP) += mmp/
- endif
- --- /dev/null
- +++ b/drivers/clk/mediatek/Makefile
- @@ -0,0 +1 @@
- +obj-y += clk-mtk.o clk-pll.o clk-gate.o
- --- /dev/null
- +++ b/drivers/clk/mediatek/clk-gate.c
- @@ -0,0 +1,137 @@
- +/*
- + * Copyright (c) 2014 MediaTek Inc.
- + * Author: James Liao <[email protected]>
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 as
- + * published by the Free Software Foundation.
- + *
- + * 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/of.h>
- +#include <linux/of_address.h>
- +
- +#include <linux/io.h>
- +#include <linux/slab.h>
- +#include <linux/delay.h>
- +#include <linux/clkdev.h>
- +
- +#include "clk-mtk.h"
- +#include "clk-gate.h"
- +
- +static int mtk_cg_bit_is_cleared(struct clk_hw *hw)
- +{
- + struct mtk_clk_gate *cg = to_clk_gate(hw);
- + u32 val;
- +
- + regmap_read(cg->regmap, cg->sta_ofs, &val);
- +
- + val &= BIT(cg->bit);
- +
- + return val == 0;
- +}
- +
- +static int mtk_cg_bit_is_set(struct clk_hw *hw)
- +{
- + struct mtk_clk_gate *cg = to_clk_gate(hw);
- + u32 val;
- +
- + regmap_read(cg->regmap, cg->sta_ofs, &val);
- +
- + val &= BIT(cg->bit);
- +
- + return val != 0;
- +}
- +
- +static void mtk_cg_set_bit(struct clk_hw *hw)
- +{
- + struct mtk_clk_gate *cg = to_clk_gate(hw);
- +
- + regmap_write(cg->regmap, cg->set_ofs, BIT(cg->bit));
- +}
- +
- +static void mtk_cg_clr_bit(struct clk_hw *hw)
- +{
- + struct mtk_clk_gate *cg = to_clk_gate(hw);
- +
- + regmap_write(cg->regmap, cg->clr_ofs, BIT(cg->bit));
- +}
- +
- +static int mtk_cg_enable(struct clk_hw *hw)
- +{
- + mtk_cg_clr_bit(hw);
- +
- + return 0;
- +}
- +
- +static void mtk_cg_disable(struct clk_hw *hw)
- +{
- + mtk_cg_set_bit(hw);
- +}
- +
- +static int mtk_cg_enable_inv(struct clk_hw *hw)
- +{
- + mtk_cg_set_bit(hw);
- +
- + return 0;
- +}
- +
- +static void mtk_cg_disable_inv(struct clk_hw *hw)
- +{
- + mtk_cg_clr_bit(hw);
- +}
- +
- +const struct clk_ops mtk_clk_gate_ops_setclr = {
- + .is_enabled = mtk_cg_bit_is_cleared,
- + .enable = mtk_cg_enable,
- + .disable = mtk_cg_disable,
- +};
- +
- +const struct clk_ops mtk_clk_gate_ops_setclr_inv = {
- + .is_enabled = mtk_cg_bit_is_set,
- + .enable = mtk_cg_enable_inv,
- + .disable = mtk_cg_disable_inv,
- +};
- +
- +struct clk *mtk_clk_register_gate(
- + const char *name,
- + const char *parent_name,
- + struct regmap *regmap,
- + int set_ofs,
- + int clr_ofs,
- + int sta_ofs,
- + u8 bit,
- + const struct clk_ops *ops)
- +{
- + struct mtk_clk_gate *cg;
- + struct clk *clk;
- + struct clk_init_data init;
- +
- + cg = kzalloc(sizeof(*cg), GFP_KERNEL);
- + if (!cg)
- + return ERR_PTR(-ENOMEM);
- +
- + init.name = name;
- + init.flags = CLK_SET_RATE_PARENT;
- + init.parent_names = parent_name ? &parent_name : NULL;
- + init.num_parents = parent_name ? 1 : 0;
- + init.ops = ops;
- +
- + cg->regmap = regmap;
- + cg->set_ofs = set_ofs;
- + cg->clr_ofs = clr_ofs;
- + cg->sta_ofs = sta_ofs;
- + cg->bit = bit;
- +
- + cg->hw.init = &init;
- +
- + clk = clk_register(NULL, &cg->hw);
- + if (IS_ERR(clk))
- + kfree(cg);
- +
- + return clk;
- +}
- --- /dev/null
- +++ b/drivers/clk/mediatek/clk-gate.h
- @@ -0,0 +1,49 @@
- +/*
- + * Copyright (c) 2014 MediaTek Inc.
- + * Author: James Liao <[email protected]>
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 as
- + * published by the Free Software Foundation.
- + *
- + * 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.
- + */
- +
- +#ifndef __DRV_CLK_GATE_H
- +#define __DRV_CLK_GATE_H
- +
- +#include <linux/regmap.h>
- +#include <linux/clk.h>
- +#include <linux/clk-provider.h>
- +
- +struct mtk_clk_gate {
- + struct clk_hw hw;
- + struct regmap *regmap;
- + int set_ofs;
- + int clr_ofs;
- + int sta_ofs;
- + u8 bit;
- +};
- +
- +static inline struct mtk_clk_gate *to_clk_gate(struct clk_hw *hw)
- +{
- + return container_of(hw, struct mtk_clk_gate, hw);
- +}
- +
- +extern const struct clk_ops mtk_clk_gate_ops_setclr;
- +extern const struct clk_ops mtk_clk_gate_ops_setclr_inv;
- +
- +struct clk *mtk_clk_register_gate(
- + const char *name,
- + const char *parent_name,
- + struct regmap *regmap,
- + int set_ofs,
- + int clr_ofs,
- + int sta_ofs,
- + u8 bit,
- + const struct clk_ops *ops);
- +
- +#endif /* __DRV_CLK_GATE_H */
- --- /dev/null
- +++ b/drivers/clk/mediatek/clk-mtk.c
- @@ -0,0 +1,220 @@
- +/*
- + * Copyright (c) 2014 MediaTek Inc.
- + * Author: James Liao <[email protected]>
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 as
- + * published by the Free Software Foundation.
- + *
- + * 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/of.h>
- +#include <linux/of_address.h>
- +#include <linux/err.h>
- +#include <linux/io.h>
- +#include <linux/slab.h>
- +#include <linux/delay.h>
- +#include <linux/clkdev.h>
- +#include <linux/mfd/syscon.h>
- +
- +#include "clk-mtk.h"
- +#include "clk-gate.h"
- +
- +struct clk_onecell_data *mtk_alloc_clk_data(unsigned int clk_num)
- +{
- + int i;
- + struct clk_onecell_data *clk_data;
- +
- + clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
- + if (!clk_data)
- + return NULL;
- +
- + clk_data->clks = kcalloc(clk_num, sizeof(*clk_data->clks), GFP_KERNEL);
- + if (!clk_data->clks)
- + goto err_out;
- +
- + clk_data->clk_num = clk_num;
- +
- + for (i = 0; i < clk_num; i++)
- + clk_data->clks[i] = ERR_PTR(-ENOENT);
- +
- + return clk_data;
- +err_out:
- + kfree(clk_data);
- +
- + return NULL;
- +}
- +
- +void mtk_clk_register_factors(const struct mtk_fixed_factor *clks, int num,
- + struct clk_onecell_data *clk_data)
- +{
- + int i;
- + struct clk *clk;
- +
- + for (i = 0; i < num; i++) {
- + const struct mtk_fixed_factor *ff = &clks[i];
- +
- + clk = clk_register_fixed_factor(NULL, ff->name, ff->parent_name,
- + CLK_SET_RATE_PARENT, ff->mult, ff->div);
- +
- + if (IS_ERR(clk)) {
- + pr_err("Failed to register clk %s: %ld\n",
- + ff->name, PTR_ERR(clk));
- + continue;
- + }
- +
- + if (clk_data)
- + clk_data->clks[ff->id] = clk;
- + }
- +}
- +
- +int mtk_clk_register_gates(struct device_node *node, const struct mtk_gate *clks,
- + int num, struct clk_onecell_data *clk_data)
- +{
- + int i;
- + struct clk *clk;
- + struct regmap *regmap;
- +
- + if (!clk_data)
- + return -ENOMEM;
- +
- + regmap = syscon_node_to_regmap(node);
- + if (IS_ERR(regmap)) {
- + pr_err("Cannot find regmap for %s: %ld\n", node->full_name,
- + PTR_ERR(regmap));
- + return PTR_ERR(regmap);
- + }
- +
- + for (i = 0; i < num; i++) {
- + const struct mtk_gate *gate = &clks[i];
- +
- + clk = mtk_clk_register_gate(gate->name, gate->parent_name,
- + regmap,
- + gate->regs->set_ofs,
- + gate->regs->clr_ofs,
- + gate->regs->sta_ofs,
- + gate->shift, gate->ops);
- +
- + if (IS_ERR(clk)) {
- + pr_err("Failed to register clk %s: %ld\n",
- + gate->name, PTR_ERR(clk));
- + continue;
- + }
- +
- + clk_data->clks[gate->id] = clk;
- + }
- +
- + return 0;
- +}
- +
- +struct clk *mtk_clk_register_composite(const struct mtk_composite *mc,
- + void __iomem *base, spinlock_t *lock)
- +{
- + struct clk *clk;
- + struct clk_mux *mux = NULL;
- + struct clk_gate *gate = NULL;
- + struct clk_divider *div = NULL;
- + struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *div_hw = NULL;
- + const struct clk_ops *mux_ops = NULL, *gate_ops = NULL, *div_ops = NULL;
- + const char * const *parent_names;
- + const char *parent;
- + int num_parents;
- + int ret;
- +
- + if (mc->mux_shift >= 0) {
- + mux = kzalloc(sizeof(*mux), GFP_KERNEL);
- + if (!mux)
- + return ERR_PTR(-ENOMEM);
- +
- + mux->reg = base + mc->mux_reg;
- + mux->mask = BIT(mc->mux_width) - 1;
- + mux->shift = mc->mux_shift;
- + mux->lock = lock;
- +
- + mux_hw = &mux->hw;
- + mux_ops = &clk_mux_ops;
- +
- + parent_names = mc->parent_names;
- + num_parents = mc->num_parents;
- + } else {
- + parent = mc->parent;
- + parent_names = &parent;
- + num_parents = 1;
- + }
- +
- + if (mc->gate_shift >= 0) {
- + gate = kzalloc(sizeof(*gate), GFP_KERNEL);
- + if (!gate) {
- + ret = -ENOMEM;
- + goto err_out;
- + }
- +
- + gate->reg = base + mc->gate_reg;
- + gate->bit_idx = mc->gate_shift;
- + gate->flags = CLK_GATE_SET_TO_DISABLE;
- + gate->lock = lock;
- +
- + gate_hw = &gate->hw;
- + gate_ops = &clk_gate_ops;
- + }
- +
- + if (mc->divider_shift >= 0) {
- + div = kzalloc(sizeof(*div), GFP_KERNEL);
- + if (!div) {
- + ret = -ENOMEM;
- + goto err_out;
- + }
- +
- + div->reg = base + mc->divider_reg;
- + div->shift = mc->divider_shift;
- + div->width = mc->divider_width;
- + div->lock = lock;
- +
- + div_hw = &div->hw;
- + div_ops = &clk_divider_ops;
- + }
- +
- + clk = clk_register_composite(NULL, mc->name, parent_names, num_parents,
- + mux_hw, mux_ops,
- + div_hw, div_ops,
- + gate_hw, gate_ops,
- + mc->flags);
- +
- + if (IS_ERR(clk)) {
- + kfree(gate);
- + kfree(mux);
- + }
- +
- + return clk;
- +err_out:
- + kfree(mux);
- +
- + return ERR_PTR(ret);
- +}
- +
- +void mtk_clk_register_composites(const struct mtk_composite *mcs,
- + int num, void __iomem *base, spinlock_t *lock,
- + struct clk_onecell_data *clk_data)
- +{
- + struct clk *clk;
- + int i;
- +
- + for (i = 0; i < num; i++) {
- + const struct mtk_composite *mc = &mcs[i];
- +
- + clk = mtk_clk_register_composite(mc, base, lock);
- +
- + if (IS_ERR(clk)) {
- + pr_err("Failed to register clk %s: %ld\n",
- + mc->name, PTR_ERR(clk));
- + continue;
- + }
- +
- + if (clk_data)
- + clk_data->clks[mc->id] = clk;
- + }
- +}
- --- /dev/null
- +++ b/drivers/clk/mediatek/clk-mtk.h
- @@ -0,0 +1,159 @@
- +/*
- + * Copyright (c) 2014 MediaTek Inc.
- + * Author: James Liao <[email protected]>
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 as
- + * published by the Free Software Foundation.
- + *
- + * 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.
- + */
- +
- +#ifndef __DRV_CLK_MTK_H
- +#define __DRV_CLK_MTK_H
- +
- +#include <linux/regmap.h>
- +#include <linux/bitops.h>
- +#include <linux/clk.h>
- +#include <linux/clk-provider.h>
- +
- +#define MAX_MUX_GATE_BIT 31
- +#define INVALID_MUX_GATE_BIT (MAX_MUX_GATE_BIT + 1)
- +
- +#define MHZ (1000 * 1000)
- +
- +struct mtk_fixed_factor {
- + int id;
- + const char *name;
- + const char *parent_name;
- + int mult;
- + int div;
- +};
- +
- +#define FACTOR(_id, _name, _parent, _mult, _div) { \
- + .id = _id, \
- + .name = _name, \
- + .parent_name = _parent, \
- + .mult = _mult, \
- + .div = _div, \
- + }
- +
- +extern void mtk_clk_register_factors(const struct mtk_fixed_factor *clks,
- + int num, struct clk_onecell_data *clk_data);
- +
- +struct mtk_composite {
- + int id;
- + const char *name;
- + const char * const * parent_names;
- + const char *parent;
- + unsigned flags;
- +
- + uint32_t mux_reg;
- + uint32_t divider_reg;
- + uint32_t gate_reg;
- +
- + signed char mux_shift;
- + signed char mux_width;
- + signed char gate_shift;
- +
- + signed char divider_shift;
- + signed char divider_width;
- +
- + signed char num_parents;
- +};
- +
- +#define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate) { \
- + .id = _id, \
- + .name = _name, \
- + .mux_reg = _reg, \
- + .mux_shift = _shift, \
- + .mux_width = _width, \
- + .gate_reg = _reg, \
- + .gate_shift = _gate, \
- + .divider_shift = -1, \
- + .parent_names = _parents, \
- + .num_parents = ARRAY_SIZE(_parents), \
- + .flags = CLK_SET_RATE_PARENT, \
- + }
- +
- +#define MUX(_id, _name, _parents, _reg, _shift, _width) { \
- + .id = _id, \
- + .name = _name, \
- + .mux_reg = _reg, \
- + .mux_shift = _shift, \
- + .mux_width = _width, \
- + .gate_shift = -1, \
- + .divider_shift = -1, \
- + .parent_names = _parents, \
- + .num_parents = ARRAY_SIZE(_parents), \
- + .flags = CLK_SET_RATE_PARENT, \
- + }
- +
- +#define DIV_GATE(_id, _name, _parent, _gate_reg, _gate_shift, _div_reg, _div_width, _div_shift) { \
- + .id = _id, \
- + .parent = _parent, \
- + .name = _name, \
- + .divider_reg = _div_reg, \
- + .divider_shift = _div_shift, \
- + .divider_width = _div_width, \
- + .gate_reg = _gate_reg, \
- + .gate_shift = _gate_shift, \
- + .mux_shift = -1, \
- + .flags = 0, \
- + }
- +
- +struct clk *mtk_clk_register_composite(const struct mtk_composite *mc,
- + void __iomem *base, spinlock_t *lock);
- +
- +void mtk_clk_register_composites(const struct mtk_composite *mcs,
- + int num, void __iomem *base, spinlock_t *lock,
- + struct clk_onecell_data *clk_data);
- +
- +struct mtk_gate_regs {
- + u32 sta_ofs;
- + u32 clr_ofs;
- + u32 set_ofs;
- +};
- +
- +struct mtk_gate {
- + int id;
- + const char *name;
- + const char *parent_name;
- + const struct mtk_gate_regs *regs;
- + int shift;
- + const struct clk_ops *ops;
- +};
- +
- +int mtk_clk_register_gates(struct device_node *node, const struct mtk_gate *clks,
- + int num, struct clk_onecell_data *clk_data);
- +
- +struct clk_onecell_data *mtk_alloc_clk_data(unsigned int clk_num);
- +
- +#define HAVE_RST_BAR BIT(0)
- +
- +struct mtk_pll_data {
- + int id;
- + const char *name;
- + uint32_t reg;
- + uint32_t pwr_reg;
- + uint32_t en_mask;
- + uint32_t pd_reg;
- + uint32_t tuner_reg;
- + int pd_shift;
- + unsigned int flags;
- + const struct clk_ops *ops;
- + u32 rst_bar_mask;
- + unsigned long fmax;
- + int pcwbits;
- + uint32_t pcw_reg;
- + int pcw_shift;
- +};
- +
- +void __init mtk_clk_register_plls(struct device_node *node,
- + const struct mtk_pll_data *plls, int num_plls,
- + struct clk_onecell_data *clk_data);
- +
- +#endif /* __DRV_CLK_MTK_H */
- --- /dev/null
- +++ b/drivers/clk/mediatek/clk-pll.c
- @@ -0,0 +1,332 @@
- +/*
- + * Copyright (c) 2014 MediaTek Inc.
- + * Author: James Liao <[email protected]>
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 as
- + * published by the Free Software Foundation.
- + *
- + * 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/of.h>
- +#include <linux/of_address.h>
- +#include <linux/io.h>
- +#include <linux/slab.h>
- +#include <linux/clkdev.h>
- +#include <linux/delay.h>
- +
- +#include "clk-mtk.h"
- +
- +#define REG_CON0 0
- +#define REG_CON1 4
- +
- +#define CON0_BASE_EN BIT(0)
- +#define CON0_PWR_ON BIT(0)
- +#define CON0_ISO_EN BIT(1)
- +#define CON0_PCW_CHG BIT(31)
- +
- +#define AUDPLL_TUNER_EN BIT(31)
- +
- +#define POSTDIV_MASK 0x7
- +#define INTEGER_BITS 7
- +
- +/*
- + * MediaTek PLLs are configured through their pcw value. The pcw value describes
- + * a divider in the PLL feedback loop which consists of 7 bits for the integer
- + * part and the remaining bits (if present) for the fractional part. Also they
- + * have a 3 bit power-of-two post divider.
- + */
- +
- +struct mtk_clk_pll {
- + struct clk_hw hw;
- + void __iomem *base_addr;
- + void __iomem *pd_addr;
- + void __iomem *pwr_addr;
- + void __iomem *tuner_addr;
- + void __iomem *pcw_addr;
- + const struct mtk_pll_data *data;
- +};
- +
- +static inline struct mtk_clk_pll *to_mtk_clk_pll(struct clk_hw *hw)
- +{
- + return container_of(hw, struct mtk_clk_pll, hw);
- +}
- +
- +static int mtk_pll_is_prepared(struct clk_hw *hw)
- +{
- + struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
- +
- + return (readl(pll->base_addr + REG_CON0) & CON0_BASE_EN) != 0;
- +}
- +
- +static unsigned long __mtk_pll_recalc_rate(struct mtk_clk_pll *pll, u32 fin,
- + u32 pcw, int postdiv)
- +{
- + int pcwbits = pll->data->pcwbits;
- + int pcwfbits;
- + u64 vco;
- + u8 c = 0;
- +
- + /* The fractional part of the PLL divider. */
- + pcwfbits = pcwbits > INTEGER_BITS ? pcwbits - INTEGER_BITS : 0;
- +
- + vco = (u64)fin * pcw;
- +
- + if (pcwfbits && (vco & GENMASK(pcwfbits - 1, 0)))
- + c = 1;
- +
- + vco >>= pcwfbits;
- +
- + if (c)
- + vco++;
- +
- + return ((unsigned long)vco + postdiv - 1) / postdiv;
- +}
- +
- +static void mtk_pll_set_rate_regs(struct mtk_clk_pll *pll, u32 pcw,
- + int postdiv)
- +{
- + u32 con1, pd, val;
- + int pll_en;
- +
- + /* set postdiv */
- + pd = readl(pll->pd_addr);
- + pd &= ~(POSTDIV_MASK << pll->data->pd_shift);
- + pd |= (ffs(postdiv) - 1) << pll->data->pd_shift;
- + writel(pd, pll->pd_addr);
- +
- + pll_en = readl(pll->base_addr + REG_CON0) & CON0_BASE_EN;
- +
- + /* set pcw */
- + val = readl(pll->pcw_addr);
- +
- + val &= ~GENMASK(pll->data->pcw_shift + pll->data->pcwbits - 1,
- + pll->data->pcw_shift);
- + val |= pcw << pll->data->pcw_shift;
- + writel(val, pll->pcw_addr);
- +
- + con1 = readl(pll->base_addr + REG_CON1);
- +
- + if (pll_en)
- + con1 |= CON0_PCW_CHG;
- +
- + writel(con1, pll->base_addr + REG_CON1);
- + if (pll->tuner_addr)
- + writel(con1 + 1, pll->tuner_addr);
- +
- + if (pll_en)
- + udelay(20);
- +}
- +
- +/*
- + * mtk_pll_calc_values - calculate good values for a given input frequency.
- + * @pll: The pll
- + * @pcw: The pcw value (output)
- + * @postdiv: The post divider (output)
- + * @freq: The desired target frequency
- + * @fin: The input frequency
- + *
- + */
- +static void mtk_pll_calc_values(struct mtk_clk_pll *pll, u32 *pcw, u32 *postdiv,
- + u32 freq, u32 fin)
- +{
- + unsigned long fmin = 1000 * MHZ;
- + u64 _pcw;
- + u32 val;
- +
- + if (freq > pll->data->fmax)
- + freq = pll->data->fmax;
- +
- + for (val = 0; val < 4; val++) {
- + *postdiv = 1 << val;
- + if (freq * *postdiv >= fmin)
- + break;
- + }
- +
- + /* _pcw = freq * postdiv / fin * 2^pcwfbits */
- + _pcw = ((u64)freq << val) << (pll->data->pcwbits - INTEGER_BITS);
- + do_div(_pcw, fin);
- +
- + *pcw = (u32)_pcw;
- +}
- +
- +static int mtk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
- + unsigned long parent_rate)
- +{
- + struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
- + u32 pcw = 0;
- + u32 postdiv;
- +
- + mtk_pll_calc_values(pll, &pcw, &postdiv, rate, parent_rate);
- + mtk_pll_set_rate_regs(pll, pcw, postdiv);
- +
- + return 0;
- +}
- +
- +static unsigned long mtk_pll_recalc_rate(struct clk_hw *hw,
- + unsigned long parent_rate)
- +{
- + struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
- + u32 postdiv;
- + u32 pcw;
- +
- + postdiv = (readl(pll->pd_addr) >> pll->data->pd_shift) & POSTDIV_MASK;
- + postdiv = 1 << postdiv;
- +
- + pcw = readl(pll->pcw_addr) >> pll->data->pcw_shift;
- + pcw &= GENMASK(pll->data->pcwbits - 1, 0);
- +
- + return __mtk_pll_recalc_rate(pll, parent_rate, pcw, postdiv);
- +}
- +
- +static long mtk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
- + unsigned long *prate)
- +{
- + struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
- + u32 pcw = 0;
- + int postdiv;
- +
- + mtk_pll_calc_values(pll, &pcw, &postdiv, rate, *prate);
- +
- + return __mtk_pll_recalc_rate(pll, *prate, pcw, postdiv);
- +}
- +
- +static int mtk_pll_prepare(struct clk_hw *hw)
- +{
- + struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
- + u32 r;
- +
- + r = readl(pll->pwr_addr) | CON0_PWR_ON;
- + writel(r, pll->pwr_addr);
- + udelay(1);
- +
- + r = readl(pll->pwr_addr) & ~CON0_ISO_EN;
- + writel(r, pll->pwr_addr);
- + udelay(1);
- +
- + r = readl(pll->base_addr + REG_CON0);
- + r |= pll->data->en_mask;
- + writel(r, pll->base_addr + REG_CON0);
- +
- + if (pll->tuner_addr) {
- + r = readl(pll->tuner_addr) | AUDPLL_TUNER_EN;
- + writel(r, pll->tuner_addr);
- + }
- +
- + udelay(20);
- +
- + if (pll->data->flags & HAVE_RST_BAR) {
- + r = readl(pll->base_addr + REG_CON0);
- + r |= pll->data->rst_bar_mask;
- + writel(r, pll->base_addr + REG_CON0);
- + }
- +
- + return 0;
- +}
- +
- +static void mtk_pll_unprepare(struct clk_hw *hw)
- +{
- + struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
- + u32 r;
- +
- + if (pll->data->flags & HAVE_RST_BAR) {
- + r = readl(pll->base_addr + REG_CON0);
- + r &= ~pll->data->rst_bar_mask;
- + writel(r, pll->base_addr + REG_CON0);
- + }
- +
- + if (pll->tuner_addr) {
- + r = readl(pll->tuner_addr) & ~AUDPLL_TUNER_EN;
- + writel(r, pll->tuner_addr);
- + }
- +
- + r = readl(pll->base_addr + REG_CON0);
- + r &= ~CON0_BASE_EN;
- + writel(r, pll->base_addr + REG_CON0);
- +
- + r = readl(pll->pwr_addr) | CON0_ISO_EN;
- + writel(r, pll->pwr_addr);
- +
- + r = readl(pll->pwr_addr) & ~CON0_PWR_ON;
- + writel(r, pll->pwr_addr);
- +}
- +
- +static const struct clk_ops mtk_pll_ops = {
- + .is_prepared = mtk_pll_is_prepared,
- + .prepare = mtk_pll_prepare,
- + .unprepare = mtk_pll_unprepare,
- + .recalc_rate = mtk_pll_recalc_rate,
- + .round_rate = mtk_pll_round_rate,
- + .set_rate = mtk_pll_set_rate,
- +};
- +
- +static struct clk *mtk_clk_register_pll(const struct mtk_pll_data *data,
- + void __iomem *base)
- +{
- + struct mtk_clk_pll *pll;
- + struct clk_init_data init;
- + struct clk *clk;
- + const char *parent_name = "clk26m";
- +
- + pll = kzalloc(sizeof(*pll), GFP_KERNEL);
- + if (!pll)
- + return ERR_PTR(-ENOMEM);
- +
- + pll->base_addr = base + data->reg;
- + pll->pwr_addr = base + data->pwr_reg;
- + pll->pd_addr = base + data->pd_reg;
- + pll->pcw_addr = base + data->pcw_reg;
- + if (data->tuner_reg)
- + pll->tuner_addr = base + data->tuner_reg;
- + pll->hw.init = &init;
- + pll->data = data;
- +
- + init.name = data->name;
- + init.ops = &mtk_pll_ops;
- + init.parent_names = &parent_name;
- + init.num_parents = 1;
- +
- + clk = clk_register(NULL, &pll->hw);
- +
- + if (IS_ERR(clk))
- + kfree(pll);
- +
- + return clk;
- +}
- +
- +void __init mtk_clk_register_plls(struct device_node *node,
- + const struct mtk_pll_data *plls, int num_plls, struct clk_onecell_data *clk_data)
- +{
- + void __iomem *base;
- + int r, i;
- + struct clk *clk;
- +
- + base = of_iomap(node, 0);
- + if (!base) {
- + pr_err("%s(): ioremap failed\n", __func__);
- + return;
- + }
- +
- + for (i = 0; i < num_plls; i++) {
- + const struct mtk_pll_data *pll = &plls[i];
- +
- + clk = mtk_clk_register_pll(pll, base);
- +
- + if (IS_ERR(clk)) {
- + pr_err("Failed to register clk %s: %ld\n",
- + pll->name, PTR_ERR(clk));
- + continue;
- + }
- +
- + clk_data->clks[pll->id] = clk;
- + }
- +
- + r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
- + if (r)
- + pr_err("%s(): could not register clock provider: %d\n",
- + __func__, r);
- +}
|