summaryrefslogtreecommitdiffstats
path: root/drivers/clk/sunxi/clk-sunxi.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-02-21 12:30:30 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2015-02-21 12:30:30 -0800
commit18a8d49973667aa016e68826eeb374788b7c63b0 (patch)
treed7f90fcf05087ce50ee931b596e60e4c3c082a29 /drivers/clk/sunxi/clk-sunxi.c
parentc189cb8ef62832f33b6cf757350a0270532a1ad8 (diff)
parentec02ace8ca0a50eef430d3676de5c5fa978b0e29 (diff)
downloadblackbird-op-linux-18a8d49973667aa016e68826eeb374788b7c63b0.tar.gz
blackbird-op-linux-18a8d49973667aa016e68826eeb374788b7c63b0.zip
Merge tag 'clk-for-linus-3.20' of git://git.linaro.org/people/mike.turquette/linux
Pull clock framework updates from Mike Turquette: "The clock framework changes contain the usual driver additions, enhancements and fixes mostly for ARM32, ARM64, MIPS and Power-based devices. Additionally the framework core underwent a bit of surgery with two major changes: - The boundary between the clock core and clock providers (e.g clock drivers) is now more well defined with dedicated provider helper functions. struct clk no longer maps 1:1 with the hardware clock but is a true per-user cookie which helps us tracker users of hardware clocks and debug bad behavior. - The addition of rate constraints for clocks. Rate ranges are now supported which are analogous to the voltage ranges in the regulator framework. Unfortunately these changes to the core created some breakeage. We think we fixed it all up but for this reason there are lots of last minute commits trying to undo the damage" * tag 'clk-for-linus-3.20' of git://git.linaro.org/people/mike.turquette/linux: (113 commits) clk: Only recalculate the rate if needed Revert "clk: mxs: Fix invalid 32-bit access to frac registers" clk: qoriq: Add support for the platform PLL powerpc/corenet: Enable CLK_QORIQ clk: Replace explicit clk assignment with __clk_hw_set_clk clk: Add __clk_hw_set_clk helper function clk: Don't dereference parent clock if is NULL MIPS: Alchemy: Remove bogus args from alchemy_clk_fgcs_detr clkdev: Always allocate a struct clk and call __clk_get() w/ CCF clk: shmobile: div6: Avoid division by zero in .round_rate() clk: mxs: Fix invalid 32-bit access to frac registers clk: omap: compile legacy omap3 clocks conditionally clkdev: Export clk_register_clkdev clk: Add rate constraints to clocks clk: remove clk-private.h pci: xgene: do not use clk-private.h arm: omap2+ remove dead clock code clk: Make clk API return per-user struct clk instances clk: tegra: Define PLLD_DSI and remove dsia(b)_mux clk: tegra: Add support for the Tegra132 CAR IP block ...
Diffstat (limited to 'drivers/clk/sunxi/clk-sunxi.c')
-rw-r--r--drivers/clk/sunxi/clk-sunxi.c262
1 files changed, 222 insertions, 40 deletions
diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c
index 1818f404538d..379324eb5486 100644
--- a/drivers/clk/sunxi/clk-sunxi.c
+++ b/drivers/clk/sunxi/clk-sunxi.c
@@ -20,11 +20,221 @@
#include <linux/of_address.h>
#include <linux/reset-controller.h>
#include <linux/spinlock.h>
+#include <linux/log2.h>
#include "clk-factors.h"
static DEFINE_SPINLOCK(clk_lock);
+/**
+ * sun6i_a31_ahb1_clk_setup() - Setup function for a31 ahb1 composite clk
+ */
+
+#define SUN6I_AHB1_MAX_PARENTS 4
+#define SUN6I_AHB1_MUX_PARENT_PLL6 3
+#define SUN6I_AHB1_MUX_SHIFT 12
+/* un-shifted mask is what mux_clk expects */
+#define SUN6I_AHB1_MUX_MASK 0x3
+#define SUN6I_AHB1_MUX_GET_PARENT(reg) ((reg >> SUN6I_AHB1_MUX_SHIFT) & \
+ SUN6I_AHB1_MUX_MASK)
+
+#define SUN6I_AHB1_DIV_SHIFT 4
+#define SUN6I_AHB1_DIV_MASK (0x3 << SUN6I_AHB1_DIV_SHIFT)
+#define SUN6I_AHB1_DIV_GET(reg) ((reg & SUN6I_AHB1_DIV_MASK) >> \
+ SUN6I_AHB1_DIV_SHIFT)
+#define SUN6I_AHB1_DIV_SET(reg, div) ((reg & ~SUN6I_AHB1_DIV_MASK) | \
+ (div << SUN6I_AHB1_DIV_SHIFT))
+#define SUN6I_AHB1_PLL6_DIV_SHIFT 6
+#define SUN6I_AHB1_PLL6_DIV_MASK (0x3 << SUN6I_AHB1_PLL6_DIV_SHIFT)
+#define SUN6I_AHB1_PLL6_DIV_GET(reg) ((reg & SUN6I_AHB1_PLL6_DIV_MASK) >> \
+ SUN6I_AHB1_PLL6_DIV_SHIFT)
+#define SUN6I_AHB1_PLL6_DIV_SET(reg, div) ((reg & ~SUN6I_AHB1_PLL6_DIV_MASK) | \
+ (div << SUN6I_AHB1_PLL6_DIV_SHIFT))
+
+struct sun6i_ahb1_clk {
+ struct clk_hw hw;
+ void __iomem *reg;
+};
+
+#define to_sun6i_ahb1_clk(_hw) container_of(_hw, struct sun6i_ahb1_clk, hw)
+
+static unsigned long sun6i_ahb1_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct sun6i_ahb1_clk *ahb1 = to_sun6i_ahb1_clk(hw);
+ unsigned long rate;
+ u32 reg;
+
+ /* Fetch the register value */
+ reg = readl(ahb1->reg);
+
+ /* apply pre-divider first if parent is pll6 */
+ if (SUN6I_AHB1_MUX_GET_PARENT(reg) == SUN6I_AHB1_MUX_PARENT_PLL6)
+ parent_rate /= SUN6I_AHB1_PLL6_DIV_GET(reg) + 1;
+
+ /* clk divider */
+ rate = parent_rate >> SUN6I_AHB1_DIV_GET(reg);
+
+ return rate;
+}
+
+static long sun6i_ahb1_clk_round(unsigned long rate, u8 *divp, u8 *pre_divp,
+ u8 parent, unsigned long parent_rate)
+{
+ u8 div, calcp, calcm = 1;
+
+ /*
+ * clock can only divide, so we will never be able to achieve
+ * frequencies higher than the parent frequency
+ */
+ if (parent_rate && rate > parent_rate)
+ rate = parent_rate;
+
+ div = DIV_ROUND_UP(parent_rate, rate);
+
+ /* calculate pre-divider if parent is pll6 */
+ if (parent == SUN6I_AHB1_MUX_PARENT_PLL6) {
+ if (div < 4)
+ calcp = 0;
+ else if (div / 2 < 4)
+ calcp = 1;
+ else if (div / 4 < 4)
+ calcp = 2;
+ else
+ calcp = 3;
+
+ calcm = DIV_ROUND_UP(div, 1 << calcp);
+ } else {
+ calcp = __roundup_pow_of_two(div);
+ calcp = calcp > 3 ? 3 : calcp;
+ }
+
+ /* we were asked to pass back divider values */
+ if (divp) {
+ *divp = calcp;
+ *pre_divp = calcm - 1;
+ }
+
+ return (parent_rate / calcm) >> calcp;
+}
+
+static long sun6i_ahb1_clk_determine_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long min_rate,
+ unsigned long max_rate,
+ unsigned long *best_parent_rate,
+ struct clk_hw **best_parent_clk)
+{
+ struct clk *clk = hw->clk, *parent, *best_parent = NULL;
+ int i, num_parents;
+ unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
+
+ /* find the parent that can help provide the fastest rate <= rate */
+ num_parents = __clk_get_num_parents(clk);
+ for (i = 0; i < num_parents; i++) {
+ parent = clk_get_parent_by_index(clk, i);
+ if (!parent)
+ continue;
+ if (__clk_get_flags(clk) & CLK_SET_RATE_PARENT)
+ parent_rate = __clk_round_rate(parent, rate);
+ else
+ parent_rate = __clk_get_rate(parent);
+
+ child_rate = sun6i_ahb1_clk_round(rate, NULL, NULL, i,
+ parent_rate);
+
+ if (child_rate <= rate && child_rate > best_child_rate) {
+ best_parent = parent;
+ best = parent_rate;
+ best_child_rate = child_rate;
+ }
+ }
+
+ if (best_parent)
+ *best_parent_clk = __clk_get_hw(best_parent);
+ *best_parent_rate = best;
+
+ return best_child_rate;
+}
+
+static int sun6i_ahb1_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sun6i_ahb1_clk *ahb1 = to_sun6i_ahb1_clk(hw);
+ unsigned long flags;
+ u8 div, pre_div, parent;
+ u32 reg;
+
+ spin_lock_irqsave(&clk_lock, flags);
+
+ reg = readl(ahb1->reg);
+
+ /* need to know which parent is used to apply pre-divider */
+ parent = SUN6I_AHB1_MUX_GET_PARENT(reg);
+ sun6i_ahb1_clk_round(rate, &div, &pre_div, parent, parent_rate);
+
+ reg = SUN6I_AHB1_DIV_SET(reg, div);
+ reg = SUN6I_AHB1_PLL6_DIV_SET(reg, pre_div);
+ writel(reg, ahb1->reg);
+
+ spin_unlock_irqrestore(&clk_lock, flags);
+
+ return 0;
+}
+
+static const struct clk_ops sun6i_ahb1_clk_ops = {
+ .determine_rate = sun6i_ahb1_clk_determine_rate,
+ .recalc_rate = sun6i_ahb1_clk_recalc_rate,
+ .set_rate = sun6i_ahb1_clk_set_rate,
+};
+
+static void __init sun6i_ahb1_clk_setup(struct device_node *node)
+{
+ struct clk *clk;
+ struct sun6i_ahb1_clk *ahb1;
+ struct clk_mux *mux;
+ const char *clk_name = node->name;
+ const char *parents[SUN6I_AHB1_MAX_PARENTS];
+ void __iomem *reg;
+ int i = 0;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+
+ /* we have a mux, we will have >1 parents */
+ while (i < SUN6I_AHB1_MAX_PARENTS &&
+ (parents[i] = of_clk_get_parent_name(node, i)) != NULL)
+ i++;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ ahb1 = kzalloc(sizeof(struct sun6i_ahb1_clk), GFP_KERNEL);
+ if (!ahb1)
+ return;
+
+ mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL);
+ if (!mux) {
+ kfree(ahb1);
+ return;
+ }
+
+ /* set up clock properties */
+ mux->reg = reg;
+ mux->shift = SUN6I_AHB1_MUX_SHIFT;
+ mux->mask = SUN6I_AHB1_MUX_MASK;
+ mux->lock = &clk_lock;
+ ahb1->reg = reg;
+
+ clk = clk_register_composite(NULL, clk_name, parents, i,
+ &mux->hw, &clk_mux_ops,
+ &ahb1->hw, &sun6i_ahb1_clk_ops,
+ NULL, NULL, 0);
+
+ if (!IS_ERR(clk)) {
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ clk_register_clkdev(clk, clk_name, NULL);
+ }
+}
+CLK_OF_DECLARE(sun6i_a31_ahb1, "allwinner,sun6i-a31-ahb1-clk", sun6i_ahb1_clk_setup);
+
/* Maximum number of parents our clocks have */
#define SUNXI_MAX_PARENTS 5
@@ -355,43 +565,6 @@ static void sun7i_a20_get_out_factors(u32 *freq, u32 parent_rate,
}
/**
- * clk_sunxi_mmc_phase_control() - configures MMC clock phase control
- */
-
-void clk_sunxi_mmc_phase_control(struct clk *clk, u8 sample, u8 output)
-{
- #define to_clk_composite(_hw) container_of(_hw, struct clk_composite, hw)
- #define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw)
-
- struct clk_hw *hw = __clk_get_hw(clk);
- struct clk_composite *composite = to_clk_composite(hw);
- struct clk_hw *rate_hw = composite->rate_hw;
- struct clk_factors *factors = to_clk_factors(rate_hw);
- unsigned long flags = 0;
- u32 reg;
-
- if (factors->lock)
- spin_lock_irqsave(factors->lock, flags);
-
- reg = readl(factors->reg);
-
- /* set sample clock phase control */
- reg &= ~(0x7 << 20);
- reg |= ((sample & 0x7) << 20);
-
- /* set output clock phase control */
- reg &= ~(0x7 << 8);
- reg |= ((output & 0x7) << 8);
-
- writel(reg, factors->reg);
-
- if (factors->lock)
- spin_unlock_irqrestore(factors->lock, flags);
-}
-EXPORT_SYMBOL(clk_sunxi_mmc_phase_control);
-
-
-/**
* sunxi_factors_clk_setup() - Setup function for factor clocks
*/
@@ -413,6 +586,7 @@ static struct clk_factors_config sun6i_a31_pll1_config = {
.kwidth = 2,
.mshift = 0,
.mwidth = 2,
+ .n_start = 1,
};
static struct clk_factors_config sun8i_a23_pll1_config = {
@@ -520,7 +694,16 @@ static const struct factors_data sun7i_a20_out_data __initconst = {
static struct clk * __init sunxi_factors_clk_setup(struct device_node *node,
const struct factors_data *data)
{
- return sunxi_factors_register(node, data, &clk_lock);
+ void __iomem *reg;
+
+ reg = of_iomap(node, 0);
+ if (!reg) {
+ pr_err("Could not get registers for factors-clk: %s\n",
+ node->name);
+ return NULL;
+ }
+
+ return sunxi_factors_register(node, data, &clk_lock, reg);
}
@@ -561,7 +744,7 @@ static void __init sunxi_mux_clk_setup(struct device_node *node,
of_property_read_string(node, "clock-output-names", &clk_name);
clk = clk_register_mux(NULL, clk_name, parents, i,
- CLK_SET_RATE_NO_REPARENT, reg,
+ CLK_SET_RATE_PARENT, reg,
data->shift, SUNXI_MUX_GATE_WIDTH,
0, &clk_lock);
@@ -1217,7 +1400,6 @@ CLK_OF_DECLARE(sun7i_a20_clk_init, "allwinner,sun7i-a20", sun5i_init_clocks);
static const char *sun6i_critical_clocks[] __initdata = {
"cpu",
- "ahb1_sdram",
};
static void __init sun6i_init_clocks(struct device_node *node)
OpenPOWER on IntegriCloud