diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index e30376c4ff5d..fa5e1d210b84 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -11,6 +11,9 @@ obj-$(CONFIG_PLAT_SPEAR) += spear/ obj-$(CONFIG_ARCH_U300) += clk-u300.o obj-$(CONFIG_COMMON_CLK_VERSATILE) += versatile/ obj-$(CONFIG_ARCH_PRIMA2) += clk-prima2.o +ifeq ($(CONFIG_COMMON_CLK), y) +obj-$(CONFIG_ARCH_MMP) += mmp/ +endif # Chip specific obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o diff --git a/drivers/clk/mmp/Makefile b/drivers/clk/mmp/Makefile new file mode 100644 index 000000000000..b5bc88cc8967 --- /dev/null +++ b/drivers/clk/mmp/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for mmp specific clk +# + +obj-y += clk-apbc.o clk-apmu.o clk-frac.o diff --git a/drivers/clk/mmp/clk-apbc.c b/drivers/clk/mmp/clk-apbc.c new file mode 100644 index 000000000000..d14120eaa71f --- /dev/null +++ b/drivers/clk/mmp/clk-apbc.c @@ -0,0 +1,152 @@ +/* + * mmp APB clock operation source file + * + * Copyright (C) 2012 Marvell + * Chao Xie + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include + +#include "clk.h" + +/* Common APB clock register bit definitions */ +#define APBC_APBCLK (1 << 0) /* APB Bus Clock Enable */ +#define APBC_FNCLK (1 << 1) /* Functional Clock Enable */ +#define APBC_RST (1 << 2) /* Reset Generation */ +#define APBC_POWER (1 << 7) /* Reset Generation */ + +#define to_clk_apbc(hw) container_of(hw, struct clk_apbc, hw) +struct clk_apbc { + struct clk_hw hw; + void __iomem *base; + unsigned int delay; + unsigned int flags; + spinlock_t *lock; +}; + +static int clk_apbc_prepare(struct clk_hw *hw) +{ + struct clk_apbc *apbc = to_clk_apbc(hw); + unsigned int data; + unsigned long flags = 0; + + /* + * It may share same register as MUX clock, + * and it will impact FNCLK enable. Spinlock is needed + */ + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + if (apbc->flags & APBC_POWER_CTRL) + data |= APBC_POWER; + data |= APBC_FNCLK; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); + + udelay(apbc->delay); + + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + data |= APBC_APBCLK; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); + + udelay(apbc->delay); + + if (!(apbc->flags & APBC_NO_BUS_CTRL)) { + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + data &= ~APBC_RST; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); + } + + return 0; +} + +static void clk_apbc_unprepare(struct clk_hw *hw) +{ + struct clk_apbc *apbc = to_clk_apbc(hw); + unsigned long data; + unsigned long flags = 0; + + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + if (apbc->flags & APBC_POWER_CTRL) + data &= ~APBC_POWER; + data &= ~APBC_FNCLK; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); + + udelay(10); + + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + data &= ~APBC_APBCLK; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); +} + +struct clk_ops clk_apbc_ops = { + .prepare = clk_apbc_prepare, + .unprepare = clk_apbc_unprepare, +}; + +struct clk *mmp_clk_register_apbc(const char *name, const char *parent_name, + void __iomem *base, unsigned int delay, + unsigned int apbc_flags, spinlock_t *lock) +{ + struct clk_apbc *apbc; + struct clk *clk; + struct clk_init_data init; + + apbc = kzalloc(sizeof(*apbc), GFP_KERNEL); + if (!apbc) + return NULL; + + init.name = name; + init.ops = &clk_apbc_ops; + init.flags = CLK_SET_RATE_PARENT; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + apbc->base = base; + apbc->delay = delay; + apbc->flags = apbc_flags; + apbc->lock = lock; + apbc->hw.init = &init; + + clk = clk_register(NULL, &apbc->hw); + if (IS_ERR(clk)) + kfree(apbc); + + return clk; +} diff --git a/drivers/clk/mmp/clk-apmu.c b/drivers/clk/mmp/clk-apmu.c new file mode 100644 index 000000000000..abe182b2377f --- /dev/null +++ b/drivers/clk/mmp/clk-apmu.c @@ -0,0 +1,97 @@ +/* + * mmp AXI peripharal clock operation source file + * + * Copyright (C) 2012 Marvell + * Chao Xie + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include + +#include "clk.h" + +#define to_clk_apmu(clk) (container_of(clk, struct clk_apmu, clk)) +struct clk_apmu { + struct clk_hw hw; + void __iomem *base; + u32 rst_mask; + u32 enable_mask; + spinlock_t *lock; +}; + +static int clk_apmu_enable(struct clk_hw *hw) +{ + struct clk_apmu *apmu = to_clk_apmu(hw); + unsigned long data; + unsigned long flags = 0; + + if (apmu->lock) + spin_lock_irqsave(apmu->lock, flags); + + data = readl_relaxed(apmu->base) | apmu->enable_mask; + writel_relaxed(data, apmu->base); + + if (apmu->lock) + spin_unlock_irqrestore(apmu->lock, flags); + + return 0; +} + +static void clk_apmu_disable(struct clk_hw *hw) +{ + struct clk_apmu *apmu = to_clk_apmu(hw); + unsigned long data; + unsigned long flags = 0; + + if (apmu->lock) + spin_lock_irqsave(apmu->lock, flags); + + data = readl_relaxed(apmu->base) & ~apmu->enable_mask; + writel_relaxed(data, apmu->base); + + if (apmu->lock) + spin_unlock_irqrestore(apmu->lock, flags); +} + +struct clk_ops clk_apmu_ops = { + .enable = clk_apmu_enable, + .disable = clk_apmu_disable, +}; + +struct clk *mmp_clk_register_apmu(const char *name, const char *parent_name, + void __iomem *base, u32 enable_mask, spinlock_t *lock) +{ + struct clk_apmu *apmu; + struct clk *clk; + struct clk_init_data init; + + apmu = kzalloc(sizeof(*apmu), GFP_KERNEL); + if (!apmu) + return NULL; + + init.name = name; + init.ops = &clk_apmu_ops; + init.flags = CLK_SET_RATE_PARENT; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + apmu->base = base; + apmu->enable_mask = enable_mask; + apmu->lock = lock; + apmu->hw.init = &init; + + clk = clk_register(NULL, &apmu->hw); + + if (IS_ERR(clk)) + kfree(apmu); + + return clk; +} diff --git a/drivers/clk/mmp/clk-frac.c b/drivers/clk/mmp/clk-frac.c new file mode 100644 index 000000000000..80c1dd15d15c --- /dev/null +++ b/drivers/clk/mmp/clk-frac.c @@ -0,0 +1,153 @@ +/* + * mmp factor clock operation source file + * + * Copyright (C) 2012 Marvell + * Chao Xie + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include + +#include "clk.h" +/* + * It is M/N clock + * + * Fout from synthesizer can be given from two equations: + * numerator/denominator = Fin / (Fout * factor) + */ + +#define to_clk_factor(hw) container_of(hw, struct clk_factor, hw) +struct clk_factor { + struct clk_hw hw; + void __iomem *base; + struct clk_factor_masks *masks; + struct clk_factor_tbl *ftbl; + unsigned int ftbl_cnt; +}; + +static long clk_factor_round_rate(struct clk_hw *hw, unsigned long drate, + unsigned long *prate) +{ + struct clk_factor *factor = to_clk_factor(hw); + unsigned long rate = 0, prev_rate; + int i; + + for (i = 0; i < factor->ftbl_cnt; i++) { + prev_rate = rate; + rate = (((*prate / 10000) * factor->ftbl[i].num) / + (factor->ftbl[i].den * factor->masks->factor)) * 10000; + if (rate > drate) + break; + } + if (i == 0) + return rate; + else + return prev_rate; +} + +static unsigned long clk_factor_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_factor *factor = to_clk_factor(hw); + struct clk_factor_masks *masks = factor->masks; + unsigned int val, num, den; + + val = readl_relaxed(factor->base); + + /* calculate numerator */ + num = (val >> masks->num_shift) & masks->num_mask; + + /* calculate denominator */ + den = (val >> masks->den_shift) & masks->num_mask; + + if (!den) + return 0; + + return (((parent_rate / 10000) * den) / + (num * factor->masks->factor)) * 10000; +} + +/* Configures new clock rate*/ +static int clk_factor_set_rate(struct clk_hw *hw, unsigned long drate, + unsigned long prate) +{ + struct clk_factor *factor = to_clk_factor(hw); + struct clk_factor_masks *masks = factor->masks; + int i; + unsigned long val; + unsigned long prev_rate, rate = 0; + + for (i = 0; i < factor->ftbl_cnt; i++) { + prev_rate = rate; + rate = (((prate / 10000) * factor->ftbl[i].num) / + (factor->ftbl[i].den * factor->masks->factor)) * 10000; + if (rate > drate) + break; + } + if (i > 0) + i--; + + val = readl_relaxed(factor->base); + + val &= ~(masks->num_mask << masks->num_shift); + val |= (factor->ftbl[i].num & masks->num_mask) << masks->num_shift; + + val &= ~(masks->den_mask << masks->den_shift); + val |= (factor->ftbl[i].den & masks->den_mask) << masks->den_shift; + + writel_relaxed(val, factor->base); + + return 0; +} + +static struct clk_ops clk_factor_ops = { + .recalc_rate = clk_factor_recalc_rate, + .round_rate = clk_factor_round_rate, + .set_rate = clk_factor_set_rate, +}; + +struct clk *mmp_clk_register_factor(const char *name, const char *parent_name, + unsigned long flags, void __iomem *base, + struct clk_factor_masks *masks, struct clk_factor_tbl *ftbl, + unsigned int ftbl_cnt) +{ + struct clk_factor *factor; + struct clk_init_data init; + struct clk *clk; + + if (!masks) { + pr_err("%s: must pass a clk_factor_mask\n", __func__); + return ERR_PTR(-EINVAL); + } + + factor = kzalloc(sizeof(*factor), GFP_KERNEL); + if (!factor) { + pr_err("%s: could not allocate factor clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + /* struct clk_aux assignments */ + factor->base = base; + factor->masks = masks; + factor->ftbl = ftbl; + factor->ftbl_cnt = ftbl_cnt; + factor->hw.init = &init; + + init.name = name; + init.ops = &clk_factor_ops; + init.flags = flags; + init.parent_names = &parent_name; + init.num_parents = 1; + + clk = clk_register(NULL, &factor->hw); + if (IS_ERR_OR_NULL(clk)) + kfree(factor); + + return clk; +} diff --git a/drivers/clk/mmp/clk.h b/drivers/clk/mmp/clk.h new file mode 100644 index 000000000000..ab86dd4a416a --- /dev/null +++ b/drivers/clk/mmp/clk.h @@ -0,0 +1,35 @@ +#ifndef __MACH_MMP_CLK_H +#define __MACH_MMP_CLK_H + +#include +#include + +#define APBC_NO_BUS_CTRL BIT(0) +#define APBC_POWER_CTRL BIT(1) + +struct clk_factor_masks { + unsigned int factor; + unsigned int num_mask; + unsigned int den_mask; + unsigned int num_shift; + unsigned int den_shift; +}; + +struct clk_factor_tbl { + unsigned int num; + unsigned int den; +}; + +extern struct clk *mmp_clk_register_pll2(const char *name, + const char *parent_name, unsigned long flags); +extern struct clk *mmp_clk_register_apbc(const char *name, + const char *parent_name, void __iomem *base, + unsigned int delay, unsigned int apbc_flags, spinlock_t *lock); +extern struct clk *mmp_clk_register_apmu(const char *name, + const char *parent_name, void __iomem *base, u32 enable_mask, + spinlock_t *lock); +extern struct clk *mmp_clk_register_factor(const char *name, + const char *parent_name, unsigned long flags, + void __iomem *base, struct clk_factor_masks *masks, + struct clk_factor_tbl *ftbl, unsigned int ftbl_cnt); +#endif