From 35739eef19f9cf37f7400c1781a95fd867820825 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 16 Apr 2013 12:55:20 +0200 Subject: ARM: MXS: Add MXS specific clk types MXS needs some special MXS specific clock types: - pll - ref (fractional divider) - busy divider (divider with additional busy bit to poll on a rate change) - lcdif (Combined clock out of a fractional divider, a divider and a gate. Signed-off-by: Sascha Hauer --- drivers/clk/mxs/Makefile | 2 + drivers/clk/mxs/clk-div.c | 112 ++++++++++++++++++++++++++++++++ drivers/clk/mxs/clk-frac.c | 136 +++++++++++++++++++++++++++++++++++++++ drivers/clk/mxs/clk-lcdif.c | 75 ++++++++++++++++++++++ drivers/clk/mxs/clk-pll.c | 117 ++++++++++++++++++++++++++++++++++ drivers/clk/mxs/clk-ref.c | 152 ++++++++++++++++++++++++++++++++++++++++++++ drivers/clk/mxs/clk.h | 52 +++++++++++++++ 7 files changed, 646 insertions(+) create mode 100644 drivers/clk/mxs/Makefile create mode 100644 drivers/clk/mxs/clk-div.c create mode 100644 drivers/clk/mxs/clk-frac.c create mode 100644 drivers/clk/mxs/clk-lcdif.c create mode 100644 drivers/clk/mxs/clk-pll.c create mode 100644 drivers/clk/mxs/clk-ref.c create mode 100644 drivers/clk/mxs/clk.h (limited to 'drivers/clk/mxs') diff --git a/drivers/clk/mxs/Makefile b/drivers/clk/mxs/Makefile new file mode 100644 index 0000000000..8b1173d8a3 --- /dev/null +++ b/drivers/clk/mxs/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_ARCH_MXS) += clk-ref.o clk-pll.o clk-frac.o clk-div.o +obj-$(CONFIG_DRIVER_VIDEO_STM) += clk-lcdif.o diff --git a/drivers/clk/mxs/clk-div.c b/drivers/clk/mxs/clk-div.c new file mode 100644 index 0000000000..e8dae25da5 --- /dev/null +++ b/drivers/clk/mxs/clk-div.c @@ -0,0 +1,112 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include + +#include "clk.h" + +/** + * struct clk_div - mxs integer divider clock + * @divider: the parent class + * @ops: pointer to clk_ops of parent class + * @reg: register address + * @busy: busy bit shift + * + * The mxs divider clock is a subclass of basic clk_divider with an + * addtional busy bit. + */ +struct clk_div { + struct clk_divider divider; + const char *parent; + const struct clk_ops *ops; + void __iomem *reg; + u8 busy; +}; + +static inline struct clk_div *to_clk_div(struct clk *clk) +{ + struct clk_divider *divider = container_of(clk, struct clk_divider, clk); + + return container_of(divider, struct clk_div, divider); +} + +static unsigned long clk_div_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_div *div = to_clk_div(clk); + + return div->ops->recalc_rate(&div->divider.clk, parent_rate); +} + +static long clk_div_round_rate(struct clk *clk, unsigned long rate, + unsigned long *prate) +{ + struct clk_div *div = to_clk_div(clk); + + return div->ops->round_rate(&div->divider.clk, rate, prate); +} + +static int clk_div_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_div *div = to_clk_div(clk); + int ret; + + ret = div->ops->set_rate(&div->divider.clk, rate, parent_rate); + if (ret) + return ret; + + if (clk_is_enabled(clk)) + while (readl(div->reg) & 1 << div->busy); + + return 0; +} + +static struct clk_ops clk_div_ops = { + .recalc_rate = clk_div_recalc_rate, + .round_rate = clk_div_round_rate, + .set_rate = clk_div_set_rate, +}; + +struct clk *mxs_clk_div(const char *name, const char *parent_name, + void __iomem *reg, u8 shift, u8 width, u8 busy) +{ + struct clk_div *div; + int ret; + + div = xzalloc(sizeof(*div)); + if (!div) + return ERR_PTR(-ENOMEM); + + div->parent = parent_name; + div->divider.clk.name = name; + div->divider.clk.ops = &clk_div_ops; + div->divider.clk.parent_names = &div->parent; + div->divider.clk.num_parents = 1; + + div->reg = reg; + div->busy = busy; + + div->divider.reg = reg; + div->divider.shift = shift; + div->divider.width = width; + div->divider.flags = CLK_DIVIDER_ONE_BASED; + div->ops = &clk_divider_ops; + + ret = clk_register(&div->divider.clk); + if (ret) + return ERR_PTR(ret); + + return &div->divider.clk; +} diff --git a/drivers/clk/mxs/clk-frac.c b/drivers/clk/mxs/clk-frac.c new file mode 100644 index 0000000000..7aa85045a4 --- /dev/null +++ b/drivers/clk/mxs/clk-frac.c @@ -0,0 +1,136 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include + +#include "clk.h" + +/** + * struct clk_frac - mxs fractional divider clock + * @hw: clk_hw for the fractional divider clock + * @reg: register address + * @shift: the divider bit shift + * @width: the divider bit width + * @busy: busy bit shift + * + * The clock is an adjustable fractional divider with a busy bit to wait + * when the divider is adjusted. + */ +struct clk_frac { + struct clk clk; + const char *parent; + void __iomem *reg; + u8 shift; + u8 width; + u8 busy; +}; + +#define to_clk_frac(_hw) container_of(_hw, struct clk_frac, clk) + +static unsigned long clk_frac_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_frac *frac = to_clk_frac(clk); + u32 div; + + div = readl(frac->reg) >> frac->shift; + div &= (1 << frac->width) - 1; + + return (parent_rate >> frac->width) * div; +} + +static long clk_frac_round_rate(struct clk *clk, unsigned long rate, + unsigned long *prate) +{ + struct clk_frac *frac = to_clk_frac(clk); + unsigned long parent_rate = *prate; + u32 div; + u64 tmp; + + if (rate > parent_rate) + return -EINVAL; + + tmp = rate; + tmp <<= frac->width; + do_div(tmp, parent_rate); + div = tmp; + + if (!div) + return -EINVAL; + + return (parent_rate >> frac->width) * div; +} + +static int clk_frac_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_frac *frac = to_clk_frac(clk); + u32 div, val; + u64 tmp; + + if (rate > parent_rate) + return -EINVAL; + + tmp = rate; + tmp <<= frac->width; + do_div(tmp, parent_rate); + div = tmp; + + if (!div) + return -EINVAL; + + val = readl(frac->reg); + val &= ~(((1 << frac->width) - 1) << frac->shift); + val |= div << frac->shift; + writel(val, frac->reg); + + if (clk_is_enabled(clk)) + while (readl(frac->reg) & 1 << frac->busy); + + return 0; +} + +static struct clk_ops clk_frac_ops = { + .recalc_rate = clk_frac_recalc_rate, + .round_rate = clk_frac_round_rate, + .set_rate = clk_frac_set_rate, +}; + +struct clk *mxs_clk_frac(const char *name, const char *parent_name, + void __iomem *reg, u8 shift, u8 width, u8 busy) +{ + struct clk_frac *frac; + int ret; + + frac = kzalloc(sizeof(*frac), GFP_KERNEL); + if (!frac) + return ERR_PTR(-ENOMEM); + + frac->parent = parent_name; + frac->clk.name = name; + frac->clk.ops = &clk_frac_ops; + frac->clk.parent_names = &frac->parent; + frac->clk.num_parents = 1; + + frac->reg = reg; + frac->shift = shift; + frac->width = width; + + ret = clk_register(&frac->clk); + if (ret) + return ERR_PTR(ret); + + return &frac->clk; +} diff --git a/drivers/clk/mxs/clk-lcdif.c b/drivers/clk/mxs/clk-lcdif.c new file mode 100644 index 0000000000..86dfe890f9 --- /dev/null +++ b/drivers/clk/mxs/clk-lcdif.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +#include "clk.h" + +struct clk_lcdif { + struct clk clk; + + struct clk *frac, *div, *gate; + const char *parent; +}; + +#define to_clk_lcdif(_hw) container_of(_hw, struct clk_lcdif, clk) + +static int clk_lcdif_set_rate(struct clk *clk, unsigned long rate, + unsigned long unused) +{ + struct clk_lcdif *lcdif = to_clk_lcdif(clk); + unsigned long frac, div, best_div = 1; + int delta, best_delta = 0x7fffffff; + unsigned long frate, rrate, best_frate; + unsigned long parent_rate = clk_get_rate(clk_get_parent(lcdif->frac)); + + best_frate = parent_rate; + + for (frac = 18; frac < 35; frac++) { + frate = (parent_rate / frac) * 18; + div = frate / rate; + if (!div) + div = 1; + rrate = frate / div; + delta = rate - rrate; + if (abs(delta) < abs(best_delta)) { + best_frate = frate; + best_div = div; + best_delta = delta; + } + } + + clk_set_rate(lcdif->frac, best_frate); + best_frate = clk_get_rate(lcdif->frac); + clk_set_rate(lcdif->div, (best_frate + best_div) / best_div); + + return 0; +} + +static const struct clk_ops clk_lcdif_ops = { + .set_rate = clk_lcdif_set_rate, +}; + +struct clk *mxs_clk_lcdif(const char *name, struct clk *frac, struct clk *div, + struct clk *gate) +{ + struct clk_lcdif *lcdif; + int ret; + + lcdif = xzalloc(sizeof(*lcdif)); + + lcdif->parent = gate->name; + lcdif->frac = frac; + lcdif->div = div; + lcdif->gate = gate; + lcdif->clk.name = name; + lcdif->clk.ops = &clk_lcdif_ops; + lcdif->clk.parent_names = &lcdif->parent; + lcdif->clk.num_parents = 1; + + ret = clk_register(&lcdif->clk); + if (ret) + return ERR_PTR(ret); + + return &lcdif->clk; +} diff --git a/drivers/clk/mxs/clk-pll.c b/drivers/clk/mxs/clk-pll.c new file mode 100644 index 0000000000..89fd6b5e31 --- /dev/null +++ b/drivers/clk/mxs/clk-pll.c @@ -0,0 +1,117 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include + +#include "clk.h" + +#define SET 0x4 +#define CLR 0x8 + +/** + * struct clk_pll - mxs pll clock + * @hw: clk_hw for the pll + * @base: base address of the pll + * @power: the shift of power bit + * @rate: the clock rate of the pll + * + * The mxs pll is a fixed rate clock with power and gate control, + * and the shift of gate bit is always 31. + */ +struct clk_pll { + struct clk clk; + const char *parent; + void __iomem *base; + u8 power; + unsigned long rate; +}; + +#define to_clk_pll(_hw) container_of(_hw, struct clk_pll, clk) + +static int clk_pll_enable(struct clk *clk) +{ + struct clk_pll *pll = to_clk_pll(clk); + + writel(1 << pll->power, pll->base + SET); + + udelay(10); + + writel(1 << 31, pll->base + CLR); + + return 0; +} + +static void clk_pll_disable(struct clk *clk) +{ + struct clk_pll *pll = to_clk_pll(clk); + + writel(1 << 31, pll->base + SET); + + writel(1 << pll->power, pll->base + CLR); +} + +static int clk_pll_is_enabled(struct clk *clk) +{ + struct clk_pll *pll = to_clk_pll(clk); + u32 val; + + val = readl(pll->base); + + if (val & (1 << 31)) + return 0; + else + return 1; +} + +static unsigned long clk_pll_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_pll *pll = to_clk_pll(clk); + + return pll->rate; +} + +static const struct clk_ops clk_pll_ops = { + .enable = clk_pll_enable, + .disable = clk_pll_disable, + .recalc_rate = clk_pll_recalc_rate, + .is_enabled = clk_pll_is_enabled, +}; + +struct clk *mxs_clk_pll(const char *name, const char *parent_name, + void __iomem *base, u8 power, unsigned long rate) +{ + struct clk_pll *pll; + int ret; + + pll = xzalloc(sizeof(*pll)); + if (!pll) + return ERR_PTR(-ENOMEM); + + pll->parent = parent_name; + pll->clk.name = name; + pll->clk.ops = &clk_pll_ops; + pll->clk.parent_names = &pll->parent; + pll->clk.num_parents = 1; + + pll->base = base; + pll->rate = rate; + pll->power = power; + + ret = clk_register(&pll->clk); + if (ret) + ERR_PTR(ret); + + return &pll->clk; +} diff --git a/drivers/clk/mxs/clk-ref.c b/drivers/clk/mxs/clk-ref.c new file mode 100644 index 0000000000..d62ebfc0dd --- /dev/null +++ b/drivers/clk/mxs/clk-ref.c @@ -0,0 +1,152 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include + +#include "clk.h" + +/** + * struct clk_ref - mxs reference clock + * @hw: clk_hw for the reference clock + * @reg: register address + * @idx: the index of the reference clock within the same register + * + * The mxs reference clock sources from pll. Every 4 reference clocks share + * one register space, and @idx is used to identify them. Each reference + * clock has a gate control and a fractional * divider. The rate is calculated + * as pll rate * (18 / FRAC), where FRAC = 18 ~ 35. + */ +struct clk_ref { + struct clk clk; + const char *parent; + void __iomem *reg; + u8 idx; +}; + +#define to_clk_ref(_hw) container_of(_hw, struct clk_ref, clk) + +#define SET 0x4 +#define CLR 0x8 + +static int clk_ref_enable(struct clk *clk) +{ + struct clk_ref *ref = to_clk_ref(clk); + + writel(1 << ((ref->idx + 1) * 8 - 1), ref->reg + CLR); + + return 0; +} + +static void clk_ref_disable(struct clk *clk) +{ + struct clk_ref *ref = to_clk_ref(clk); + + writel(1 << ((ref->idx + 1) * 8 - 1), ref->reg + SET); +} + +static unsigned long clk_ref_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_ref *ref = to_clk_ref(clk); + u64 tmp = parent_rate; + u8 frac = (readl(ref->reg) >> (ref->idx * 8)) & 0x3f; + + tmp *= 18; + do_div(tmp, frac); + + return tmp; +} + +static long clk_ref_round_rate(struct clk *clk, unsigned long rate, + unsigned long *prate) +{ + unsigned long parent_rate = *prate; + u64 tmp = parent_rate; + u32 frac; + + tmp = tmp * 18 + rate / 2; + do_div(tmp, rate); + frac = tmp; + + if (frac < 18) + frac = 18; + else if (frac > 35) + frac = 35; + + tmp = parent_rate; + tmp *= 18; + do_div(tmp, frac); + + return tmp; +} + +static int clk_ref_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_ref *ref = to_clk_ref(clk); + u64 tmp = parent_rate; + u32 val; + u32 frac, shift = ref->idx * 8; + + tmp = tmp * 18 + rate / 2; + do_div(tmp, rate); + frac = tmp; + + if (frac < 18) + frac = 18; + else if (frac > 35) + frac = 35; + + val = readl(ref->reg); + val &= ~(0x3f << shift); + val |= frac << shift; + writel(val, ref->reg); + + return 0; +} + +static const struct clk_ops clk_ref_ops = { + .enable = clk_ref_enable, + .disable = clk_ref_disable, + .recalc_rate = clk_ref_recalc_rate, + .round_rate = clk_ref_round_rate, + .set_rate = clk_ref_set_rate, +}; + +struct clk *mxs_clk_ref(const char *name, const char *parent_name, + void __iomem *reg, u8 idx) +{ + struct clk_ref *ref; + int ret; + + ref = xzalloc(sizeof(*ref)); + if (!ref) + return ERR_PTR(-ENOMEM); + + ref->parent = parent_name; + ref->clk.name = name; + ref->clk.ops = &clk_ref_ops; + ref->clk.parent_names = &ref->parent; + ref->clk.num_parents = 1; + + ref->reg = reg; + ref->idx = idx; + + ret = clk_register(&ref->clk); + if (ret) + return ERR_PTR(ret); + + return &ref->clk; +} diff --git a/drivers/clk/mxs/clk.h b/drivers/clk/mxs/clk.h new file mode 100644 index 0000000000..b4fcfa0090 --- /dev/null +++ b/drivers/clk/mxs/clk.h @@ -0,0 +1,52 @@ +#ifndef __MXS_CLK_H +#define __MXS_CLK_H + +int mxs_clk_wait(void __iomem *reg, u8 shift); + +struct clk *mxs_clk_pll(const char *name, const char *parent_name, + void __iomem *base, u8 power, unsigned long rate); + +struct clk *mxs_clk_ref(const char *name, const char *parent_name, + void __iomem *reg, u8 idx); + +struct clk *mxs_clk_div(const char *name, const char *parent_name, + void __iomem *reg, u8 shift, u8 width, u8 busy); + +struct clk *mxs_clk_frac(const char *name, const char *parent_name, + void __iomem *reg, u8 shift, u8 width, u8 busy); + +#ifdef CONFIG_DRIVER_VIDEO_STM +struct clk *mxs_clk_lcdif(const char *name, struct clk *frac, struct clk *div, + struct clk *gate); +#else +static inline struct clk *mxs_clk_lcdif(const char *name, struct clk *frac, struct clk *div, + struct clk *gate) +{ + return ERR_PTR(-ENOSYS); +} +#endif + +static inline struct clk *mxs_clk_fixed(const char *name, int rate) +{ + return clk_fixed(name, rate); +} + +static inline struct clk *mxs_clk_gate(const char *name, + const char *parent_name, void __iomem *reg, u8 shift) +{ + return clk_gate_inverted(name, parent_name, reg, shift); +} + +static inline struct clk *mxs_clk_mux(const char *name, void __iomem *reg, + u8 shift, u8 width, const char **parent_names, int num_parents) +{ + return clk_mux(name, reg, shift, width, parent_names, num_parents); +} + +static inline struct clk *mxs_clk_fixed_factor(const char *name, + const char *parent_name, unsigned int mult, unsigned int div) +{ + return clk_fixed_factor(name, parent_name, mult, div); +} + +#endif /* __MXS_CLK_H */ -- cgit v1.2.3