[PATCH v2 2/2] clk: rockchip: Add support for the mmc clock phases using the framework

2014-11-14 Thread Alexandru M Stan
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 60ps long) to
generate a rough 45deg (which might be from 33deg to 66deg) setting.

Suggested-by: Heiko Stuebner 
Signed-off-by: Alexandru M Stan 
---
Changes in v2:
- fixed my cc/to list
- removed dangling #DEFINE DEBUG

 drivers/clk/rockchip/Makefile|   1 +
 drivers/clk/rockchip/clk-mmc-phase.c | 149 +++
 drivers/clk/rockchip/clk-rk3288.c|  12 +++
 drivers/clk/rockchip/clk.c   |   8 ++
 drivers/clk/rockchip/clk.h   |  23 ++
 5 files changed, 193 insertions(+)
 create mode 100644 drivers/clk/rockchip/clk-mmc-phase.c

diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile
index bd8514d..2714097 100644
--- a/drivers/clk/rockchip/Makefile
+++ b/drivers/clk/rockchip/Makefile
@@ -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
index 000..763df4b
--- /dev/null
+++ b/drivers/clk/rockchip/clk-mmc-phase.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2014 Google, Inc
+ * Author: Alexandru M Stan 
+ *
+ * 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 
+#include 
+#include "clk.h"
+
+struct rockchip_mmc_clock_phase {
+   struct clk_hw   hw;
+   void __iomem*reg;
+   int id;
+   int shift;
+};
+
+#define to_phase(_hw) container_of(_hw, struct rockchip_mmc_clock_phase, hw)
+
+static unsigned long rockchip_mmc_recalc(struct clk_hw *hw,
+unsigned long parent_rate)
+{
+   return parent_rate / 2;
+}
+
+#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 1LL
+
+/*
+ * 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 DELAY_ELEMENT_PSEC 60
+
+static int rockchip_mmc_get_phase(struct clk_hw *hw)
+{
+   struct rockchip_mmc_clock_phase *phase = to_phase(hw);
+   unsigned long rate = clk_get_rate(hw->clk);
+   u32 raw_value;
+   u16 degrees;
+   u8 delay_num = 0;
+
+   raw_value = readl(phase->reg) >> (phase->shift);
+
+   degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90;
+
+   if (raw_value & ROCKCHIP_MMC_DELAY_SEL) {
+   /* degrees/delaynum * 1 */
+   unsigned long factor = (DELAY_ELEMENT_PSEC / 10) * 36 *
+   (rate / 100);
+
+   delay_num = (raw_value & ROCKCHIP_MMC_DELAYNUM_MASK);
+   delay_num >>= ROCKCHIP_MMC_DELAYNUM_OFFSET;
+   degrees += delay_num * factor / 1;
+   }
+
+   return degrees % 360;
+}
+
+static int rockchip_mmc_set_phase(struct clk_hw *hw, int degrees)
+{
+   struct rockchip_mmc_clock_phase *phase = to_phase(hw);
+   unsigned long rate = clk_get_rate(hw->clk);
+   u8 nineties, is_fortyfive;
+   u8 delay_num = 0;
+   u32 raw_value;
+
+   degrees -= degrees % 45;
+
+   nineties = degrees / 90;
+   is_fortyfive = (degrees % 90) == 45;
+
+   if (is_fortyfive) {
+   u64 delay;
+
+   delay = PSECS_PER_SEC;
+   do_div(delay, rate);
+   do_div(delay, 360 / 45);
+   do_div(delay, DELAY_ELEMENT_PSEC);
+
+   delay_num = (u8) min(delay, 255ULL);
+   }
+
+   raw_value = ROCKCHIP_MMC_DELAY_SEL;
+   raw_value |= delay_num << ROCKCHIP_MMC_DELAYNUM_OFFSET;
+   raw_value |= nineties;
+   writel(HIWORD_UPDATE(raw_value, 0x, phase->shift), phase->reg);
+
+   pr_debug("%s->set_phase(%d) delay_nums=%u reg[0x%p]=0x%x 
actual_degrees=%d\n",
+   __clk_get_name(hw->clk), degrees, delay_num,
+   phase->reg, raw_value>>phase->shift,
+   rockchip_mmc_get_phase(hw)
+   );
+
+   return 0;
+}
+
+static const struct clk_ops rockchip_phase_clk_ops = {
+   

[PATCH v2 2/2] clk: rockchip: Add support for the mmc clock phases using the framework

2014-11-14 Thread Alexandru M Stan
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 60ps long) to
generate a rough 45deg (which might be from 33deg to 66deg) setting.

Suggested-by: Heiko Stuebner he...@sntech.de
Signed-off-by: Alexandru M Stan ams...@chromium.org
---
Changes in v2:
- fixed my cc/to list
- removed dangling #DEFINE DEBUG

 drivers/clk/rockchip/Makefile|   1 +
 drivers/clk/rockchip/clk-mmc-phase.c | 149 +++
 drivers/clk/rockchip/clk-rk3288.c|  12 +++
 drivers/clk/rockchip/clk.c   |   8 ++
 drivers/clk/rockchip/clk.h   |  23 ++
 5 files changed, 193 insertions(+)
 create mode 100644 drivers/clk/rockchip/clk-mmc-phase.c

diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile
index bd8514d..2714097 100644
--- a/drivers/clk/rockchip/Makefile
+++ b/drivers/clk/rockchip/Makefile
@@ -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
index 000..763df4b
--- /dev/null
+++ b/drivers/clk/rockchip/clk-mmc-phase.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2014 Google, Inc
+ * Author: Alexandru M Stan ams...@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_phase {
+   struct clk_hw   hw;
+   void __iomem*reg;
+   int id;
+   int shift;
+};
+
+#define to_phase(_hw) container_of(_hw, struct rockchip_mmc_clock_phase, hw)
+
+static unsigned long rockchip_mmc_recalc(struct clk_hw *hw,
+unsigned long parent_rate)
+{
+   return parent_rate / 2;
+}
+
+#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 1LL
+
+/*
+ * 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 DELAY_ELEMENT_PSEC 60
+
+static int rockchip_mmc_get_phase(struct clk_hw *hw)
+{
+   struct rockchip_mmc_clock_phase *phase = to_phase(hw);
+   unsigned long rate = clk_get_rate(hw-clk);
+   u32 raw_value;
+   u16 degrees;
+   u8 delay_num = 0;
+
+   raw_value = readl(phase-reg)  (phase-shift);
+
+   degrees = (raw_value  ROCKCHIP_MMC_DEGREE_MASK) * 90;
+
+   if (raw_value  ROCKCHIP_MMC_DELAY_SEL) {
+   /* degrees/delaynum * 1 */
+   unsigned long factor = (DELAY_ELEMENT_PSEC / 10) * 36 *
+   (rate / 100);
+
+   delay_num = (raw_value  ROCKCHIP_MMC_DELAYNUM_MASK);
+   delay_num = ROCKCHIP_MMC_DELAYNUM_OFFSET;
+   degrees += delay_num * factor / 1;
+   }
+
+   return degrees % 360;
+}
+
+static int rockchip_mmc_set_phase(struct clk_hw *hw, int degrees)
+{
+   struct rockchip_mmc_clock_phase *phase = to_phase(hw);
+   unsigned long rate = clk_get_rate(hw-clk);
+   u8 nineties, is_fortyfive;
+   u8 delay_num = 0;
+   u32 raw_value;
+
+   degrees -= degrees % 45;
+
+   nineties = degrees / 90;
+   is_fortyfive = (degrees % 90) == 45;
+
+   if (is_fortyfive) {
+   u64 delay;
+
+   delay = PSECS_PER_SEC;
+   do_div(delay, rate);
+   do_div(delay, 360 / 45);
+   do_div(delay, DELAY_ELEMENT_PSEC);
+
+   delay_num = (u8) min(delay, 255ULL);
+   }
+
+   raw_value = ROCKCHIP_MMC_DELAY_SEL;
+   raw_value |= delay_num  ROCKCHIP_MMC_DELAYNUM_OFFSET;
+   raw_value |= nineties;
+   writel(HIWORD_UPDATE(raw_value, 0x, phase-shift), phase-reg);
+
+   pr_debug(%s-set_phase(%d) delay_nums=%u reg[0x%p]=0x%x 
actual_degrees=%d\n,
+   __clk_get_name(hw-clk), degrees, delay_num,
+   phase-reg, raw_valuephase-shift,
+   rockchip_mmc_get_phase(hw)
+   );
+
+   return 0;
+}
+
+static