clk: rockchip: add new clock-type for the ddrclk
authorLin Huang <hl@rock-chips.com>
Mon, 22 Aug 2016 03:36:17 +0000 (11:36 +0800)
committerHeiko Stuebner <heiko@sntech.de>
Thu, 1 Sep 2016 09:23:56 +0000 (11:23 +0200)
Changing the rate of the DDR clock needs special care, as the DDR
is of course in use and will react badly if the rate changes under it.

Over time different approaches to handle that were used.

Past SoCs like the rk3288 and before would store some code in SRAM
while the rk3368 used a SCPI variant and let a coprocessor handle that.

New rockchip platforms like the rk3399 have a dcf controller to do ddr
frequency scaling, and support for this controller will be implemented
in the arm-trusted-firmware.

This new clock-type should over time handle all these methods for
handling DDR rate changes, but right now it will concentrate on the
SIP interface used to talk to ARM trusted firmware.

The SIP interface counterpart was merged from pull-request #684 [0]
into the upstream arm-trusted-firmware codebase.

[0] https://github.com/ARM-software/arm-trusted-firmware/pull/684

Signed-off-by: Lin Huang <hl@rock-chips.com>
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
drivers/clk/rockchip/Makefile
drivers/clk/rockchip/clk-ddr.c [new file with mode: 0644]
drivers/clk/rockchip/clk.c
drivers/clk/rockchip/clk.h

index f47a2fa962d2e881d004bbc0067502ce6cc07704..b5f2c8ed12e17641f810b0e941102cad2cf34b7f 100644 (file)
@@ -8,6 +8,7 @@ obj-y   += clk-pll.o
 obj-y  += clk-cpu.o
 obj-y  += clk-inverter.o
 obj-y  += clk-mmc-phase.o
+obj-y  += clk-ddr.o
 obj-$(CONFIG_RESET_CONTROLLER) += softrst.o
 
 obj-y  += clk-rk3036.o
diff --git a/drivers/clk/rockchip/clk-ddr.c b/drivers/clk/rockchip/clk-ddr.c
new file mode 100644 (file)
index 0000000..8feba93
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2016 Rockchip Electronics Co. Ltd.
+ * Author: Lin Huang <hl@rock-chips.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/arm-smccc.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <soc/rockchip/rockchip_sip.h>
+#include "clk.h"
+
+struct rockchip_ddrclk {
+       struct clk_hw   hw;
+       void __iomem    *reg_base;
+       int             mux_offset;
+       int             mux_shift;
+       int             mux_width;
+       int             div_shift;
+       int             div_width;
+       int             ddr_flag;
+       spinlock_t      *lock;
+};
+
+#define to_rockchip_ddrclk_hw(hw) container_of(hw, struct rockchip_ddrclk, hw)
+
+static int rockchip_ddrclk_sip_set_rate(struct clk_hw *hw, unsigned long drate,
+                                       unsigned long prate)
+{
+       struct rockchip_ddrclk *ddrclk = to_rockchip_ddrclk_hw(hw);
+       unsigned long flags;
+       struct arm_smccc_res res;
+
+       spin_lock_irqsave(ddrclk->lock, flags);
+       arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, drate, 0,
+                     ROCKCHIP_SIP_CONFIG_DRAM_SET_RATE,
+                     0, 0, 0, 0, &res);
+       spin_unlock_irqrestore(ddrclk->lock, flags);
+
+       return res.a0;
+}
+
+static unsigned long
+rockchip_ddrclk_sip_recalc_rate(struct clk_hw *hw,
+                               unsigned long parent_rate)
+{
+       struct arm_smccc_res res;
+
+       arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0,
+                     ROCKCHIP_SIP_CONFIG_DRAM_GET_RATE,
+                     0, 0, 0, 0, &res);
+
+       return res.a0;
+}
+
+static long rockchip_ddrclk_sip_round_rate(struct clk_hw *hw,
+                                          unsigned long rate,
+                                          unsigned long *prate)
+{
+       struct arm_smccc_res res;
+
+       arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, rate, 0,
+                     ROCKCHIP_SIP_CONFIG_DRAM_ROUND_RATE,
+                     0, 0, 0, 0, &res);
+
+       return res.a0;
+}
+
+static u8 rockchip_ddrclk_get_parent(struct clk_hw *hw)
+{
+       struct rockchip_ddrclk *ddrclk = to_rockchip_ddrclk_hw(hw);
+       int num_parents = clk_hw_get_num_parents(hw);
+       u32 val;
+
+       val = clk_readl(ddrclk->reg_base +
+                       ddrclk->mux_offset) >> ddrclk->mux_shift;
+       val &= GENMASK(ddrclk->mux_width - 1, 0);
+
+       if (val >= num_parents)
+               return -EINVAL;
+
+       return val;
+}
+
+static const struct clk_ops rockchip_ddrclk_sip_ops = {
+       .recalc_rate = rockchip_ddrclk_sip_recalc_rate,
+       .set_rate = rockchip_ddrclk_sip_set_rate,
+       .round_rate = rockchip_ddrclk_sip_round_rate,
+       .get_parent = rockchip_ddrclk_get_parent,
+};
+
+struct clk *rockchip_clk_register_ddrclk(const char *name, int flags,
+                                        const char *const *parent_names,
+                                        u8 num_parents, int mux_offset,
+                                        int mux_shift, int mux_width,
+                                        int div_shift, int div_width,
+                                        int ddr_flag, void __iomem *reg_base,
+                                        spinlock_t *lock)
+{
+       struct rockchip_ddrclk *ddrclk;
+       struct clk_init_data init;
+       struct clk *clk;
+
+       ddrclk = kzalloc(sizeof(*ddrclk), GFP_KERNEL);
+       if (!ddrclk)
+               return ERR_PTR(-ENOMEM);
+
+       init.name = name;
+       init.parent_names = parent_names;
+       init.num_parents = num_parents;
+
+       init.flags = flags;
+       init.flags |= CLK_SET_RATE_NO_REPARENT;
+
+       switch (ddr_flag) {
+       case ROCKCHIP_DDRCLK_SIP:
+               init.ops = &rockchip_ddrclk_sip_ops;
+               break;
+       default:
+               pr_err("%s: unsupported ddrclk type %d\n", __func__, ddr_flag);
+               kfree(ddrclk);
+               return ERR_PTR(-EINVAL);
+       }
+
+       ddrclk->reg_base = reg_base;
+       ddrclk->lock = lock;
+       ddrclk->hw.init = &init;
+       ddrclk->mux_offset = mux_offset;
+       ddrclk->mux_shift = mux_shift;
+       ddrclk->mux_width = mux_width;
+       ddrclk->div_shift = div_shift;
+       ddrclk->div_width = div_width;
+       ddrclk->ddr_flag = ddr_flag;
+
+       clk = clk_register(NULL, &ddrclk->hw);
+       if (IS_ERR(clk)) {
+               pr_err("%s: could not register ddrclk %s\n", __func__,  name);
+               kfree(ddrclk);
+               return NULL;
+       }
+
+       return clk;
+}
index 1f1c74f3744b7e163e5aac5ca51d7fadd34d28a5..b886be30f34f7136a143d0d76bf5b5cde4da86f2 100644 (file)
@@ -484,6 +484,15 @@ void __init rockchip_clk_register_branches(
                                list->gate_offset, list->gate_shift,
                                list->gate_flags, flags, &ctx->lock);
                        break;
+               case branch_ddrclk:
+                       clk = rockchip_clk_register_ddrclk(
+                               list->name, list->flags,
+                               list->parent_names, list->num_parents,
+                               list->muxdiv_offset, list->mux_shift,
+                               list->mux_width, list->div_shift,
+                               list->div_width, list->div_flags,
+                               ctx->reg_base, &ctx->lock);
+                       break;
                }
 
                /* none of the cases above matched */
index 3747de5ce7c231875d6b2f16a98da089ed08b2da..1653edd792a5ebb66c5007afcce6d262e028078e 100644 (file)
@@ -281,6 +281,20 @@ struct clk *rockchip_clk_register_mmc(const char *name,
                                const char *const *parent_names, u8 num_parents,
                                void __iomem *reg, int shift);
 
+/*
+ * DDRCLK flags, including method of setting the rate
+ * ROCKCHIP_DDRCLK_SIP: use SIP call to bl31 to change ddrclk rate.
+ */
+#define ROCKCHIP_DDRCLK_SIP            BIT(0)
+
+struct clk *rockchip_clk_register_ddrclk(const char *name, int flags,
+                                        const char *const *parent_names,
+                                        u8 num_parents, int mux_offset,
+                                        int mux_shift, int mux_width,
+                                        int div_shift, int div_width,
+                                        int ddr_flags, void __iomem *reg_base,
+                                        spinlock_t *lock);
+
 #define ROCKCHIP_INVERTER_HIWORD_MASK  BIT(0)
 
 struct clk *rockchip_clk_register_inverter(const char *name,
@@ -299,6 +313,7 @@ enum rockchip_clk_branch_type {
        branch_mmc,
        branch_inverter,
        branch_factor,
+       branch_ddrclk,
 };
 
 struct rockchip_clk_branch {
@@ -488,6 +503,24 @@ struct rockchip_clk_branch {
                .child          = ch,                           \
        }
 
+#define COMPOSITE_DDRCLK(_id, cname, pnames, f, mo, ms, mw,    \
+                        ds, dw, df)                            \
+       {                                                       \
+               .id             = _id,                          \
+               .branch_type    = branch_ddrclk,                \
+               .name           = cname,                        \
+               .parent_names   = pnames,                       \
+               .num_parents    = ARRAY_SIZE(pnames),           \
+               .flags          = f,                            \
+               .muxdiv_offset  = mo,                           \
+               .mux_shift      = ms,                           \
+               .mux_width      = mw,                           \
+               .div_shift      = ds,                           \
+               .div_width      = dw,                           \
+               .div_flags      = df,                           \
+               .gate_offset    = -1,                           \
+       }
+
 #define MUX(_id, cname, pnames, f, o, s, w, mf)                        \
        {                                                       \
                .id             = _id,                          \