[PATCH v4 6/8] clk: ingenic: Add JZ47xx TCU clocks driver

2018-03-17 Thread Paul Cercueil
The TCU (Timer Counter Unit) of the Ingenic JZ47xx SoCs features 8
channels, each one having its own clock, that can be started and
stopped, reparented, and reclocked.

This driver only modifies the bits of the registers of the TCU that are
related to clocks control. It provides one clock per TCU channel (plus
one for the watchdog and one for the OS timer) that can be used by other
drivers.

Signed-off-by: Paul Cercueil 
Acked-by: Stephen Boyd 
---
 drivers/clk/ingenic/Makefile |   2 +-
 drivers/clk/ingenic/tcu.c| 319 +++
 2 files changed, 320 insertions(+), 1 deletion(-)
 create mode 100644 drivers/clk/ingenic/tcu.c

 v2: - Use SPDX identifier for the license
 - Fix broken build caused by typo when correcting patch
 v3: - Move documentation to its own patch
 - Get rid of ingenic_tcu structure. The "struct regmap *map" was
   added to struct ingenic_tcu_clk, the other fields are gone.
 - Replace clk_register / clk_register_clockdev /
   of_clk_add_provider with their "clk_hw" variant.
 v4: No change

diff --git a/drivers/clk/ingenic/Makefile b/drivers/clk/ingenic/Makefile
index 1456e4cdb562..9dcadd4fed4c 100644
--- a/drivers/clk/ingenic/Makefile
+++ b/drivers/clk/ingenic/Makefile
@@ -1,4 +1,4 @@
-obj-y  += cgu.o
+obj-y  += cgu.o tcu.o
 obj-$(CONFIG_MACH_JZ4740)  += jz4740-cgu.o
 obj-$(CONFIG_MACH_JZ4770)  += jz4770-cgu.o
 obj-$(CONFIG_MACH_JZ4780)  += jz4780-cgu.o
diff --git a/drivers/clk/ingenic/tcu.c b/drivers/clk/ingenic/tcu.c
new file mode 100644
index ..b550b6ac8fb4
--- /dev/null
+++ b/drivers/clk/ingenic/tcu.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Ingenic JZ47xx SoC TCU clocks driver
+ * Copyright (C) 2018 Paul Cercueil 
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+enum ingenic_version {
+   ID_JZ4740,
+   ID_JZ4770,
+   ID_JZ4780,
+};
+
+struct ingenic_tcu_clk_info {
+   struct clk_init_data init_data;
+   u8 gate_bit;
+   u8 tcsr_reg;
+};
+
+struct ingenic_tcu_clk {
+   struct clk_hw hw;
+
+   struct regmap *map;
+   const struct ingenic_tcu_clk_info *info;
+
+   unsigned int idx;
+};
+
+#define to_tcu_clk(_hw) container_of(_hw, struct ingenic_tcu_clk, hw)
+
+static int ingenic_tcu_enable(struct clk_hw *hw)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+
+   regmap_write(tcu_clk->map, TCU_REG_TSCR, BIT(info->gate_bit));
+   return 0;
+}
+
+static void ingenic_tcu_disable(struct clk_hw *hw)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+
+   regmap_write(tcu_clk->map, TCU_REG_TSSR, BIT(info->gate_bit));
+}
+
+static int ingenic_tcu_is_enabled(struct clk_hw *hw)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+   unsigned int value;
+
+   regmap_read(tcu_clk->map, TCU_REG_TSR, );
+
+   return !(value & BIT(info->gate_bit));
+}
+
+static u8 ingenic_tcu_get_parent(struct clk_hw *hw)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+   unsigned int val = 0;
+   int ret;
+
+   ret = regmap_read(tcu_clk->map, info->tcsr_reg, );
+   WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
+
+   return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
+}
+
+static int ingenic_tcu_set_parent(struct clk_hw *hw, u8 idx)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+   struct regmap *map = tcu_clk->map;
+   int ret;
+
+   /*
+* Our clock provider has the CLK_SET_PARENT_GATE flag set, so we know
+* that the clk is in unprepared state. To be able to access TCSR
+* we must ungate the clock supply and we gate it again when done.
+*/
+
+   regmap_write(map, TCU_REG_TSCR, BIT(info->gate_bit));
+
+   ret = regmap_update_bits(map, info->tcsr_reg,
+   TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
+   WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
+
+   regmap_write(map, TCU_REG_TSSR, BIT(info->gate_bit));
+
+   return 0;
+}
+
+static unsigned long ingenic_tcu_recalc_rate(struct clk_hw *hw,
+   unsigned long parent_rate)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+   unsigned int prescale;
+   int ret;
+
+   ret = regmap_read(tcu_clk->map, info->tcsr_reg, );
+   WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
+
+   prescale = (prescale & 

[PATCH v4 6/8] clk: ingenic: Add JZ47xx TCU clocks driver

2018-03-17 Thread Paul Cercueil
The TCU (Timer Counter Unit) of the Ingenic JZ47xx SoCs features 8
channels, each one having its own clock, that can be started and
stopped, reparented, and reclocked.

This driver only modifies the bits of the registers of the TCU that are
related to clocks control. It provides one clock per TCU channel (plus
one for the watchdog and one for the OS timer) that can be used by other
drivers.

Signed-off-by: Paul Cercueil 
Acked-by: Stephen Boyd 
---
 drivers/clk/ingenic/Makefile |   2 +-
 drivers/clk/ingenic/tcu.c| 319 +++
 2 files changed, 320 insertions(+), 1 deletion(-)
 create mode 100644 drivers/clk/ingenic/tcu.c

 v2: - Use SPDX identifier for the license
 - Fix broken build caused by typo when correcting patch
 v3: - Move documentation to its own patch
 - Get rid of ingenic_tcu structure. The "struct regmap *map" was
   added to struct ingenic_tcu_clk, the other fields are gone.
 - Replace clk_register / clk_register_clockdev /
   of_clk_add_provider with their "clk_hw" variant.
 v4: No change

diff --git a/drivers/clk/ingenic/Makefile b/drivers/clk/ingenic/Makefile
index 1456e4cdb562..9dcadd4fed4c 100644
--- a/drivers/clk/ingenic/Makefile
+++ b/drivers/clk/ingenic/Makefile
@@ -1,4 +1,4 @@
-obj-y  += cgu.o
+obj-y  += cgu.o tcu.o
 obj-$(CONFIG_MACH_JZ4740)  += jz4740-cgu.o
 obj-$(CONFIG_MACH_JZ4770)  += jz4770-cgu.o
 obj-$(CONFIG_MACH_JZ4780)  += jz4780-cgu.o
diff --git a/drivers/clk/ingenic/tcu.c b/drivers/clk/ingenic/tcu.c
new file mode 100644
index ..b550b6ac8fb4
--- /dev/null
+++ b/drivers/clk/ingenic/tcu.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Ingenic JZ47xx SoC TCU clocks driver
+ * Copyright (C) 2018 Paul Cercueil 
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+enum ingenic_version {
+   ID_JZ4740,
+   ID_JZ4770,
+   ID_JZ4780,
+};
+
+struct ingenic_tcu_clk_info {
+   struct clk_init_data init_data;
+   u8 gate_bit;
+   u8 tcsr_reg;
+};
+
+struct ingenic_tcu_clk {
+   struct clk_hw hw;
+
+   struct regmap *map;
+   const struct ingenic_tcu_clk_info *info;
+
+   unsigned int idx;
+};
+
+#define to_tcu_clk(_hw) container_of(_hw, struct ingenic_tcu_clk, hw)
+
+static int ingenic_tcu_enable(struct clk_hw *hw)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+
+   regmap_write(tcu_clk->map, TCU_REG_TSCR, BIT(info->gate_bit));
+   return 0;
+}
+
+static void ingenic_tcu_disable(struct clk_hw *hw)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+
+   regmap_write(tcu_clk->map, TCU_REG_TSSR, BIT(info->gate_bit));
+}
+
+static int ingenic_tcu_is_enabled(struct clk_hw *hw)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+   unsigned int value;
+
+   regmap_read(tcu_clk->map, TCU_REG_TSR, );
+
+   return !(value & BIT(info->gate_bit));
+}
+
+static u8 ingenic_tcu_get_parent(struct clk_hw *hw)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+   unsigned int val = 0;
+   int ret;
+
+   ret = regmap_read(tcu_clk->map, info->tcsr_reg, );
+   WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
+
+   return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
+}
+
+static int ingenic_tcu_set_parent(struct clk_hw *hw, u8 idx)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+   struct regmap *map = tcu_clk->map;
+   int ret;
+
+   /*
+* Our clock provider has the CLK_SET_PARENT_GATE flag set, so we know
+* that the clk is in unprepared state. To be able to access TCSR
+* we must ungate the clock supply and we gate it again when done.
+*/
+
+   regmap_write(map, TCU_REG_TSCR, BIT(info->gate_bit));
+
+   ret = regmap_update_bits(map, info->tcsr_reg,
+   TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
+   WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
+
+   regmap_write(map, TCU_REG_TSSR, BIT(info->gate_bit));
+
+   return 0;
+}
+
+static unsigned long ingenic_tcu_recalc_rate(struct clk_hw *hw,
+   unsigned long parent_rate)
+{
+   struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+   const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+   unsigned int prescale;
+   int ret;
+
+   ret = regmap_read(tcu_clk->map, info->tcsr_reg, );
+   WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
+
+   prescale = (prescale & TCU_TCSR_PRESCALE_MASK) >> TCU_TCSR_PRESCALE_LSB;
+
+   return