clk: rockchip: Add support for the mmc clock phases using the framework
authorAlexandru M Stan <amstan@chromium.org>
Thu, 27 Nov 2014 01:30:27 +0000 (17:30 -0800)
committerHeiko Stuebner <heiko@sntech.de>
Thu, 27 Nov 2014 23:44:24 +0000 (00:44 +0100)
This patch adds the 2 physical clocks for the mmc (drive and sample). They're
mostly there for the phase properties, but they also show the true clock
(by dividing by RK3288_MMC_CLKGEN_DIV).

The drive and sample phases are generated by dividing an upstream parent clock
by 2, this allows us to adjust the phase by 90 deg.

There's also an option to have up to 255 delay elements (40-80 picoseconds long).
This driver uses those elements (under the assumption that they're 60 ps long)
to generate approximate 22.5 degrees options. 67.5 (22.5*3) might be as high as
90 deg if the delay elements are as big as 80 ps, so a finer division (smaller
than 22.5) was not picked because the phase might not be monotonic anymore.

Suggested-by: Heiko Stuebner <heiko@sntech.de>
Signed-off-by: Alexandru M Stan <amstan@chromium.org>
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
drivers/clk/rockchip/Makefile
drivers/clk/rockchip/clk-mmc-phase.c [new file with mode: 0644]
drivers/clk/rockchip/clk-rk3288.c
drivers/clk/rockchip/clk.c
drivers/clk/rockchip/clk.h

index bd8514d63634bdb78ae0e75f801be478265f5f91..2714097f90db1138b1f71771461b15932b826cce 100644 (file)
@@ -6,6 +6,7 @@ obj-y   += clk-rockchip.o
 obj-y  += clk.o
 obj-y  += clk-pll.o
 obj-y  += clk-cpu.o
+obj-y  += clk-mmc-phase.o
 obj-$(CONFIG_RESET_CONTROLLER) += softrst.o
 
 obj-y  += clk-rk3188.o
diff --git a/drivers/clk/rockchip/clk-mmc-phase.c b/drivers/clk/rockchip/clk-mmc-phase.c
new file mode 100644 (file)
index 0000000..c842e3b
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2014 Google, Inc
+ * Author: Alexandru M Stan <amstan@chromium.org>
+ *
+ * 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/slab.h>
+#include <linux/clk-provider.h>
+#include "clk.h"
+
+struct rockchip_mmc_clock {
+       struct clk_hw   hw;
+       void __iomem    *reg;
+       int             id;
+       int             shift;
+};
+
+#define to_mmc_clock(_hw) container_of(_hw, struct rockchip_mmc_clock, hw)
+
+#define RK3288_MMC_CLKGEN_DIV 2
+
+static unsigned long rockchip_mmc_recalc(struct clk_hw *hw,
+                                        unsigned long parent_rate)
+{
+       return parent_rate / RK3288_MMC_CLKGEN_DIV;
+}
+
+#define ROCKCHIP_MMC_DELAY_SEL BIT(10)
+#define ROCKCHIP_MMC_DEGREE_MASK 0x3
+#define ROCKCHIP_MMC_DELAYNUM_OFFSET 2
+#define ROCKCHIP_MMC_DELAYNUM_MASK (0xff << ROCKCHIP_MMC_DELAYNUM_OFFSET)
+
+#define PSECS_PER_SEC 1000000000000LL
+
+/*
+ * Each fine delay is between 40ps-80ps. Assume each fine delay is 60ps to
+ * simplify calculations. So 45degs could be anywhere between 33deg and 66deg.
+ */
+#define ROCKCHIP_MMC_DELAY_ELEMENT_PSEC 60
+
+static int rockchip_mmc_get_phase(struct clk_hw *hw)
+{
+       struct rockchip_mmc_clock *mmc_clock = to_mmc_clock(hw);
+       unsigned long rate = clk_get_rate(hw->clk);
+       u32 raw_value;
+       u16 degrees;
+       u32 delay_num = 0;
+
+       raw_value = readl(mmc_clock->reg) >> (mmc_clock->shift);
+
+       degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90;
+
+       if (raw_value & ROCKCHIP_MMC_DELAY_SEL) {
+               /* degrees/delaynum * 10000 */
+               unsigned long factor = (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10) *
+                                       36 * (rate / 1000000);
+
+               delay_num = (raw_value & ROCKCHIP_MMC_DELAYNUM_MASK);
+               delay_num >>= ROCKCHIP_MMC_DELAYNUM_OFFSET;
+               degrees += delay_num * factor / 10000;
+       }
+
+       return degrees % 360;
+}
+
+static int rockchip_mmc_set_phase(struct clk_hw *hw, int degrees)
+{
+       struct rockchip_mmc_clock *mmc_clock = to_mmc_clock(hw);
+       unsigned long rate = clk_get_rate(hw->clk);
+       u8 nineties, remainder;
+       u8 delay_num;
+       u32 raw_value;
+       u64 delay;
+
+       /* allow 22 to be 22.5 */
+       degrees++;
+       /* floor to 22.5 increment */
+       degrees -= ((degrees) * 10 % 225) / 10;
+
+       nineties = degrees / 90;
+       /* 22.5 multiples */
+       remainder = (degrees % 90) / 22;
+
+       delay = PSECS_PER_SEC;
+       do_div(delay, rate);
+       /* / 360 / 22.5 */
+       do_div(delay, 16);
+       do_div(delay, ROCKCHIP_MMC_DELAY_ELEMENT_PSEC);
+
+       delay *= remainder;
+       delay_num = (u8) min(delay, 255ULL);
+
+       raw_value = delay_num ? ROCKCHIP_MMC_DELAY_SEL : 0;
+       raw_value |= delay_num << ROCKCHIP_MMC_DELAYNUM_OFFSET;
+       raw_value |= nineties;
+       writel(HIWORD_UPDATE(raw_value, 0x07ff, mmc_clock->shift), mmc_clock->reg);
+
+       pr_debug("%s->set_phase(%d) delay_nums=%u reg[0x%p]=0x%03x actual_degrees=%d\n",
+               __clk_get_name(hw->clk), degrees, delay_num,
+               mmc_clock->reg, raw_value>>(mmc_clock->shift),
+               rockchip_mmc_get_phase(hw)
+       );
+
+       return 0;
+}
+
+static const struct clk_ops rockchip_mmc_clk_ops = {
+       .recalc_rate    = rockchip_mmc_recalc,
+       .get_phase      = rockchip_mmc_get_phase,
+       .set_phase      = rockchip_mmc_set_phase,
+};
+
+struct clk *rockchip_clk_register_mmc(const char *name,
+                               const char **parent_names, u8 num_parents,
+                               void __iomem *reg, int shift)
+{
+       struct clk_init_data init;
+       struct rockchip_mmc_clock *mmc_clock;
+       struct clk *clk;
+
+       mmc_clock = kmalloc(sizeof(*mmc_clock), GFP_KERNEL);
+       if (!mmc_clock)
+               return NULL;
+
+       init.num_parents = num_parents;
+       init.parent_names = parent_names;
+       init.ops = &rockchip_mmc_clk_ops;
+
+       mmc_clock->hw.init = &init;
+       mmc_clock->reg = reg;
+       mmc_clock->shift = shift;
+
+       if (name)
+               init.name = name;
+
+       clk = clk_register(NULL, &mmc_clock->hw);
+       if (IS_ERR(clk))
+               goto err_free;
+
+       return clk;
+
+err_free:
+       kfree(mmc_clock);
+       return NULL;
+}
index a43045b05d9833c3af2a42bc51d34ff102d78667..ac6be7c0132d1e27cfad2f95169212b70f3d31d2 100644 (file)
@@ -486,6 +486,18 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = {
                        RK3288_CLKSEL_CON(12), 14, 2, MFLAGS, 8, 6, DFLAGS,
                        RK3288_CLKGATE_CON(13), 3, GFLAGS),
 
+       MMC(SCLK_SDMMC_DRV,    "sdmmc_drv",    "sclk_sdmmc", RK3288_SDMMC_CON0, 1),
+       MMC(SCLK_SDMMC_SAMPLE, "sdmmc_sample", "sclk_sdmmc", RK3288_SDMMC_CON1, 0),
+
+       MMC(SCLK_SDIO0_DRV,    "sdio0_drv",    "sclk_sdio0", RK3288_SDIO0_CON0, 1),
+       MMC(SCLK_SDIO0_SAMPLE, "sdio0_sample", "sclk_sdio0", RK3288_SDIO0_CON1, 0),
+
+       MMC(SCLK_SDIO1_DRV,    "sdio1_drv",    "sclk_sdio1", RK3288_SDIO1_CON0, 1),
+       MMC(SCLK_SDIO1_SAMPLE, "sdio1_sample", "sclk_sdio1", RK3288_SDIO1_CON1, 0),
+
+       MMC(SCLK_EMMC_DRV,     "emmc_drv",     "sclk_emmc",  RK3288_EMMC_CON0,  1),
+       MMC(SCLK_EMMC_SAMPLE,  "emmc_sample",  "sclk_emmc",  RK3288_EMMC_CON1,  0),
+
        COMPOSITE(0, "sclk_tspout", mux_tspout_p, 0,
                        RK3288_CLKSEL_CON(35), 14, 2, MFLAGS, 8, 5, DFLAGS,
                        RK3288_CLKGATE_CON(4), 11, GFLAGS),
index 3b8f26e2cd1aec297f8dde82e21ab176aa5c3eed..f6150da97e8ba323091bc8d47d1e38b292dc1b64 100644 (file)
@@ -271,6 +271,14 @@ void __init rockchip_clk_register_branches(
                                list->gate_offset, list->gate_shift,
                                list->gate_flags, flags, &clk_lock);
                        break;
+               case branch_mmc:
+                       clk = rockchip_clk_register_mmc(
+                               list->name,
+                               list->parent_names, list->num_parents,
+                               reg_base + list->muxdiv_offset,
+                               list->div_shift
+                       );
+                       break;
                }
 
                /* none of the cases above matched */
index dc9468132ef19eac7d776218442009fd88239105..58d2e3bdf22fed499f8b4893eaf48690433d0298 100644 (file)
 #define RK3288_GLB_SRST_SND            0x1b4
 #define RK3288_SOFTRST_CON(x)          (x * 0x4 + 0x1b8)
 #define RK3288_MISC_CON                        0x1e8
+#define RK3288_SDMMC_CON0              0x200
+#define RK3288_SDMMC_CON1              0x204
+#define RK3288_SDIO0_CON0              0x208
+#define RK3288_SDIO0_CON1              0x20c
+#define RK3288_SDIO1_CON0              0x210
+#define RK3288_SDIO1_CON1              0x214
+#define RK3288_EMMC_CON0               0x218
+#define RK3288_EMMC_CON1               0x21c
 
 enum rockchip_pll_type {
        pll_rk3066,
@@ -170,6 +178,10 @@ struct clk *rockchip_clk_register_cpuclk(const char *name,
                        const struct rockchip_cpuclk_rate_table *rates,
                        int nrates, void __iomem *reg_base, spinlock_t *lock);
 
+struct clk *rockchip_clk_register_mmc(const char *name,
+                               const char **parent_names, u8 num_parents,
+                               void __iomem *reg, int shift);
+
 #define PNAME(x) static const char *x[] __initconst
 
 enum rockchip_clk_branch_type {
@@ -178,6 +190,7 @@ enum rockchip_clk_branch_type {
        branch_divider,
        branch_fraction_divider,
        branch_gate,
+       branch_mmc,
 };
 
 struct rockchip_clk_branch {
@@ -370,6 +383,16 @@ struct rockchip_clk_branch {
                .gate_flags     = gf,                           \
        }
 
+#define MMC(_id, cname, pname, offset, shift)                  \
+       {                                                       \
+               .id             = _id,                          \
+               .branch_type    = branch_mmc,                   \
+               .name           = cname,                        \
+               .parent_names   = (const char *[]){ pname },    \
+               .num_parents    = 1,                            \
+               .muxdiv_offset  = offset,                       \
+               .div_shift      = shift,                        \
+       }
 
 void rockchip_clk_init(struct device_node *np, void __iomem *base,
                       unsigned long nr_clks);