[Letux-kernel] [RFC 07/28] clock/ingenic/tcu: add JZ4730 definitons
H. Nikolaus Schaller
hns at goldelico.com
Sat Jan 23 17:28:33 CET 2021
From: Paul Boddie <paul at boddie.org.uk>
Add jz4730 TCU clock definitions and to the existing ingenic tcu driver.
Signed-off-by: Paul Boddie <paul at boddie.org.uk>
Signed-off-by: H. Nikolaus Schaller <hns at goldelico.com>
---
drivers/clk/ingenic/tcu.c | 185 +++++++++++++++++++++++++++-----
include/linux/mfd/ingenic-tcu.h | 25 ++++-
2 files changed, 180 insertions(+), 30 deletions(-)
diff --git a/drivers/clk/ingenic/tcu.c b/drivers/clk/ingenic/tcu.c
index 9382dc3aa27e6..91392ecb6d013 100644
--- a/drivers/clk/ingenic/tcu.c
+++ b/drivers/clk/ingenic/tcu.c
@@ -2,6 +2,7 @@
/*
* JZ47xx SoCs TCU clocks driver
* Copyright (C) 2019 Paul Cercueil <paul at crapouillou.net>
+ * Copyright (C) 2019, 2020 Paul Boddie <paul at boddie.org.uk>
*/
#include <linux/clk.h>
@@ -22,15 +23,21 @@
#define pr_fmt(fmt) "ingenic-tcu-clk: " fmt
enum tcu_clk_parent {
- TCU_PARENT_PCLK,
- TCU_PARENT_RTC,
- TCU_PARENT_EXT,
-};
-struct ingenic_soc_info {
- unsigned int num_channels;
- bool has_ost;
- bool has_tcu_clk;
+ /* TCU_TCSRx bit values. */
+
+ TCU_PARENT_PCLK = 0,
+ TCU_PARENT_RTC = 1,
+ TCU_PARENT_EXT = 2,
+
+ /* OST_TCSRx bit values. */
+
+ TCU_JZ4730_PARENT_PCLK_DIV_4 = 0,
+ TCU_JZ4730_PARENT_PCLK_DIV_16 = 1,
+ TCU_JZ4730_PARENT_PCLK_DIV_64 = 2,
+ TCU_JZ4730_PARENT_PCLK_DIV_256 = 3,
+ TCU_JZ4730_PARENT_RTC = 4,
+ TCU_JZ4730_PARENT_EXT = 5,
};
struct ingenic_tcu_clk_info {
@@ -39,6 +46,18 @@ struct ingenic_tcu_clk_info {
u8 tcsr_reg;
};
+struct ingenic_soc_info {
+ unsigned int num_channels;
+ bool has_ost;
+ bool has_wdt;
+ bool has_tcu_clk;
+ bool has_prescale;
+ bool jz4740_regs;
+ const struct ingenic_tcu_clk_info *clk_info;
+ enum tcu_clk_parent parent_rtc;
+ enum tcu_clk_parent parent_ext;
+};
+
struct ingenic_tcu_clk {
struct clk_hw hw;
unsigned int idx;
@@ -67,7 +86,10 @@ static int ingenic_tcu_enable(struct clk_hw *hw)
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
struct ingenic_tcu *tcu = tcu_clk->tcu;
- regmap_write(tcu->map, TCU_REG_TSCR, BIT(info->gate_bit));
+ if (tcu->soc_info->jz4740_regs)
+ regmap_write(tcu->map, TCU_REG_TSCR, BIT(info->gate_bit));
+ else
+ regmap_set_bits(tcu->map, TCU_JZ4730_REG_TER, BIT(info->gate_bit));
return 0;
}
@@ -78,7 +100,10 @@ static void ingenic_tcu_disable(struct clk_hw *hw)
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
struct ingenic_tcu *tcu = tcu_clk->tcu;
- regmap_write(tcu->map, TCU_REG_TSSR, BIT(info->gate_bit));
+ if (tcu->soc_info->jz4740_regs)
+ regmap_write(tcu->map, TCU_REG_TSSR, BIT(info->gate_bit));
+ else
+ regmap_clear_bits(tcu->map, TCU_JZ4730_REG_TER, BIT(info->gate_bit));
}
static int ingenic_tcu_is_enabled(struct clk_hw *hw)
@@ -87,7 +112,10 @@ static int ingenic_tcu_is_enabled(struct clk_hw *hw)
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
unsigned int value;
- regmap_read(tcu_clk->tcu->map, TCU_REG_TSR, &value);
+ if (tcu_clk->tcu->soc_info->jz4740_regs)
+ regmap_read(tcu_clk->tcu->map, TCU_REG_TSR, &value);
+ else
+ regmap_read(tcu_clk->tcu->map, TCU_JZ4730_REG_TER, &value);
return !(value & BIT(info->gate_bit));
}
@@ -107,7 +135,10 @@ static bool ingenic_tcu_enable_regs(struct clk_hw *hw)
*/
if (!tcu->clk) {
enabled = !!ingenic_tcu_is_enabled(hw);
- regmap_write(tcu->map, TCU_REG_TSCR, BIT(info->gate_bit));
+ if (tcu->soc_info->jz4740_regs)
+ regmap_write(tcu->map, TCU_REG_TER, BIT(info->gate_bit));
+ else
+ regmap_set_bits(tcu->map, TCU_JZ4730_REG_TER, BIT(info->gate_bit));
}
return enabled;
@@ -119,34 +150,48 @@ static void ingenic_tcu_disable_regs(struct clk_hw *hw)
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
struct ingenic_tcu *tcu = tcu_clk->tcu;
- if (!tcu->clk)
- regmap_write(tcu->map, TCU_REG_TSSR, BIT(info->gate_bit));
+ if (!tcu->clk) {
+ if (tcu->soc_info->jz4740_regs)
+ regmap_write(tcu->map, TCU_REG_TSSR, BIT(info->gate_bit));
+ else
+ regmap_clear_bits(tcu->map, TCU_JZ4730_REG_TER, 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;
+ struct ingenic_tcu *tcu = tcu_clk->tcu;
unsigned int val = 0;
int ret;
ret = regmap_read(tcu_clk->tcu->map, info->tcsr_reg, &val);
WARN_ONCE(ret < 0, "Unable to read TCSR %d", tcu_clk->idx);
- return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
+ if (tcu->soc_info->jz4740_regs)
+ return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
+ else
+ return val & TCU_JZ4730_TCSR_PARENT_CLOCK_MASK;
}
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 ingenic_tcu *tcu = tcu_clk->tcu;
bool was_enabled;
int ret;
was_enabled = ingenic_tcu_enable_regs(hw);
- ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg,
- TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
+ if (tcu->soc_info->jz4740_regs) {
+ ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg,
+ TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
+ } else {
+ ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg,
+ TCU_JZ4730_TCSR_PARENT_CLOCK_MASK, idx);
+ }
WARN_ONCE(ret < 0, "Unable to update TCSR %d", tcu_clk->idx);
if (!was_enabled)
@@ -191,6 +236,9 @@ static long ingenic_tcu_round_rate(struct clk_hw *hw, unsigned long req_rate,
if (req_rate > rate)
return rate;
+ if (!to_tcu_clk(hw)->tcu->soc_info->has_prescale)
+ return rate;
+
prescale = ingenic_tcu_get_prescale(rate, req_rate);
return rate >> (prescale * 2);
@@ -201,10 +249,14 @@ static int ingenic_tcu_set_rate(struct clk_hw *hw, unsigned long req_rate,
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
- u8 prescale = ingenic_tcu_get_prescale(parent_rate, req_rate);
+ u8 prescale;
bool was_enabled;
int ret;
+ if (!tcu_clk->tcu->soc_info->has_prescale)
+ return 0;
+
+ prescale = ingenic_tcu_get_prescale(parent_rate, req_rate);
was_enabled = ingenic_tcu_enable_regs(hw);
ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg,
@@ -237,6 +289,15 @@ static const char * const ingenic_tcu_timer_parents[] = {
[TCU_PARENT_EXT] = "ext",
};
+static const char * const ingenic_tcu_timer_jz4730_parents[] = {
+ [TCU_JZ4730_PARENT_PCLK_DIV_4] = "pclk_div_4",
+ [TCU_JZ4730_PARENT_PCLK_DIV_16] = "pclk_div_16",
+ [TCU_JZ4730_PARENT_PCLK_DIV_64] = "pclk_div_64",
+ [TCU_JZ4730_PARENT_PCLK_DIV_256] = "pclk_div_256",
+ [TCU_JZ4730_PARENT_RTC] = "rtc",
+ [TCU_JZ4730_PARENT_EXT] = "ext",
+};
+
#define DEF_TIMER(_name, _gate_bit, _tcsr) \
{ \
.init_data = { \
@@ -260,11 +321,30 @@ static const struct ingenic_tcu_clk_info ingenic_tcu_clk_info[] = {
[TCU_CLK_TIMER7] = DEF_TIMER("timer7", 7, TCU_REG_TCSRc(7)),
};
+#define DEF_TIMER_JZ4730(_name, _gate_bit, _tcsr) \
+ { \
+ .init_data = { \
+ .name = _name, \
+ .parent_names = ingenic_tcu_timer_jz4730_parents,\
+ .num_parents = ARRAY_SIZE(ingenic_tcu_timer_jz4730_parents),\
+ .ops = &ingenic_tcu_clk_ops, \
+ .flags = CLK_SET_RATE_UNGATE, \
+ }, \
+ .gate_bit = _gate_bit, \
+ .tcsr_reg = _tcsr, \
+ }
+static const struct ingenic_tcu_clk_info ingenic_tcu_jz4730_clk_info[] = {
+ [TCU_CLK_TIMER0] = DEF_TIMER_JZ4730("timer0", 0, TCU_JZ4730_REG_TCSRc(0)),
+ [TCU_CLK_TIMER1] = DEF_TIMER_JZ4730("timer1", 1, TCU_JZ4730_REG_TCSRc(1)),
+ [TCU_CLK_TIMER2] = DEF_TIMER_JZ4730("timer2", 2, TCU_JZ4730_REG_TCSRc(2)),
+};
+
static const struct ingenic_tcu_clk_info ingenic_tcu_watchdog_clk_info =
DEF_TIMER("wdt", 16, TCU_REG_WDT_TCSR);
static const struct ingenic_tcu_clk_info ingenic_tcu_ost_clk_info =
DEF_TIMER("ost", 15, TCU_REG_OST_TCSR);
#undef DEF_TIMER
+#undef DEF_TIMER_JZ4730
static int __init ingenic_tcu_register_clock(struct ingenic_tcu *tcu,
unsigned int idx, enum tcu_clk_parent parent,
@@ -285,7 +365,12 @@ static int __init ingenic_tcu_register_clock(struct ingenic_tcu *tcu,
/* Reset channel and clock divider, set default parent */
ingenic_tcu_enable_regs(&tcu_clk->hw);
- regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent));
+ if (tcu->soc_info->jz4740_regs) {
+ regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent));
+ } else {
+ regmap_update_bits(tcu->map, info->tcsr_reg,
+ 0xffff & ~TCU_JZ4730_TCSR_EN, parent);
+ }
ingenic_tcu_disable_regs(&tcu_clk->hw);
err = clk_hw_register(NULL, &tcu_clk->hw);
@@ -302,30 +387,67 @@ static int __init ingenic_tcu_register_clock(struct ingenic_tcu *tcu,
static const struct ingenic_soc_info jz4740_soc_info = {
.num_channels = 8,
.has_ost = false,
+ .has_wdt = true,
.has_tcu_clk = true,
+ .has_prescale = true,
+ .jz4740_regs = true,
+ .clk_info = ingenic_tcu_clk_info,
+ .parent_rtc = TCU_PARENT_RTC,
+ .parent_ext = TCU_PARENT_EXT,
};
static const struct ingenic_soc_info jz4725b_soc_info = {
.num_channels = 6,
.has_ost = true,
+ .has_wdt = true,
+ .has_tcu_clk = true,
+ .has_prescale = true,
+ .jz4740_regs = true,
+ .clk_info = ingenic_tcu_clk_info,
+ .parent_rtc = TCU_PARENT_RTC,
+ .parent_ext = TCU_PARENT_EXT,
+};
+
+static const struct ingenic_soc_info jz4730_soc_info = {
+ .num_channels = 3,
+ .has_ost = false, /* JZ4730 uses OST channels as TCU channels */
+ .has_wdt = false, /* JZ4730 has a separate watchdog timer */
.has_tcu_clk = true,
+ .has_prescale = false, /* JZ4730 TCSR has no apparent prescale field */
+ .jz4740_regs = false, /* JZ4730 uses different register layout */
+ .clk_info = ingenic_tcu_jz4730_clk_info,
+ .parent_rtc = TCU_JZ4730_PARENT_RTC,
+ .parent_ext = TCU_JZ4730_PARENT_EXT,
};
static const struct ingenic_soc_info jz4770_soc_info = {
.num_channels = 8,
.has_ost = true,
+ .has_wdt = true,
.has_tcu_clk = false,
+ .has_prescale = true,
+ .jz4740_regs = true,
+ .clk_info = ingenic_tcu_clk_info,
+ .parent_rtc = TCU_PARENT_RTC,
+ .parent_ext = TCU_PARENT_EXT,
};
static const struct ingenic_soc_info x1000_soc_info = {
.num_channels = 8,
.has_ost = false, /* X1000 has OST, but it not belong TCU */
+ .has_wdt = true,
.has_tcu_clk = false,
+ .has_prescale = true,
+ .jz4740_regs = true,
+ .clk_info = ingenic_tcu_clk_info,
+ .parent_rtc = TCU_PARENT_RTC,
+ .parent_ext = TCU_PARENT_EXT,
};
static const struct of_device_id __maybe_unused ingenic_tcu_of_match[] __initconst = {
{ .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, },
{ .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, },
+ { .compatible = "ingenic,jz4730-tcu", .data = &jz4730_soc_info, },
{ .compatible = "ingenic,jz4770-tcu", .data = &jz4770_soc_info, },
{ .compatible = "ingenic,x1000-tcu", .data = &x1000_soc_info, },
{ /* sentinel */ }
@@ -375,8 +497,8 @@ static int __init ingenic_tcu_probe(struct device_node *np)
tcu->clocks->num = TCU_CLK_COUNT;
for (i = 0; i < tcu->soc_info->num_channels; i++) {
- ret = ingenic_tcu_register_clock(tcu, i, TCU_PARENT_EXT,
- &ingenic_tcu_clk_info[i],
+ ret = ingenic_tcu_register_clock(tcu, i, tcu->soc_info->parent_ext,
+ &tcu->soc_info->clk_info[i],
tcu->clocks);
if (ret) {
pr_crit("cannot register clock %d\n", i);
@@ -391,17 +513,19 @@ static int __init ingenic_tcu_probe(struct device_node *np)
* the watchdog would kick after a maximum time of 5s, and we might
* want a slower kicking time.
*/
- ret = ingenic_tcu_register_clock(tcu, TCU_CLK_WDT, TCU_PARENT_RTC,
- &ingenic_tcu_watchdog_clk_info,
- tcu->clocks);
- if (ret) {
- pr_crit("cannot register watchdog clock\n");
- goto err_unregister_timer_clocks;
+ if (tcu->soc_info->has_wdt) {
+ ret = ingenic_tcu_register_clock(tcu, TCU_CLK_WDT, tcu->soc_info->parent_rtc,
+ &ingenic_tcu_watchdog_clk_info,
+ tcu->clocks);
+ if (ret) {
+ pr_crit("cannot register watchdog clock\n");
+ goto err_unregister_timer_clocks;
+ }
}
if (tcu->soc_info->has_ost) {
ret = ingenic_tcu_register_clock(tcu, TCU_CLK_OST,
- TCU_PARENT_EXT,
+ tcu->soc_info->parent_ext,
&ingenic_tcu_ost_clk_info,
tcu->clocks);
if (ret) {
@@ -424,7 +548,8 @@ static int __init ingenic_tcu_probe(struct device_node *np)
if (tcu->soc_info->has_ost)
clk_hw_unregister(tcu->clocks->hws[i + 1]);
err_unregister_watchdog_clock:
- clk_hw_unregister(tcu->clocks->hws[i]);
+ if (tcu->soc_info->has_wdt)
+ clk_hw_unregister(tcu->clocks->hws[i]);
err_unregister_timer_clocks:
for (i = 0; i < tcu->clocks->num; i++)
if (tcu->clocks->hws[i])
@@ -477,5 +602,7 @@ static void __init ingenic_tcu_init(struct device_node *np)
CLK_OF_DECLARE_DRIVER(jz4740_cgu, "ingenic,jz4740-tcu", ingenic_tcu_init);
CLK_OF_DECLARE_DRIVER(jz4725b_cgu, "ingenic,jz4725b-tcu", ingenic_tcu_init);
+CLK_OF_DECLARE_DRIVER(jz4730_cgu, "ingenic,jz4730-tcu", ingenic_tcu_init);
CLK_OF_DECLARE_DRIVER(jz4770_cgu, "ingenic,jz4770-tcu", ingenic_tcu_init);
CLK_OF_DECLARE_DRIVER(x1000_cgu, "ingenic,x1000-tcu", ingenic_tcu_init);
+MODULE_AUTHOR("Paul Boddie <paul at boddie.org.uk>");
diff --git a/include/linux/mfd/ingenic-tcu.h b/include/linux/mfd/ingenic-tcu.h
index 2083fa20821df..c040308945a7d 100644
--- a/include/linux/mfd/ingenic-tcu.h
+++ b/include/linux/mfd/ingenic-tcu.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+// SPDX-License-Identifier: GPL-2.0
/*
* Header file for the Ingenic JZ47xx TCU driver
*/
@@ -53,4 +53,27 @@
#define TCU_REG_TCNTc(c) (TCU_REG_TCNT0 + ((c) * TCU_CHANNEL_STRIDE))
#define TCU_REG_TCSRc(c) (TCU_REG_TCSR0 + ((c) * TCU_CHANNEL_STRIDE))
+/* JZ4730 register layout. */
+
+#define WDT_JZ4730_REG_TCSR 0x00
+#define WDT_JZ4730_REG_TCNT 0x04
+
+#define WDT_JZ4730_TCSR_EN BIT(4) /* Watchdog timer enable */
+
+#define TCU_JZ4730_REG_TER 0x00
+#define TCU_JZ4730_REG_TRDR0 0x10
+#define TCU_JZ4730_REG_TCNT0 0x14
+#define TCU_JZ4730_REG_TCSR0 0x18
+
+#define TCU_JZ4730_TCSR_PARENT_CLOCK_MASK 0x07
+
+#define TCU_JZ4730_TCSR_BUSY BIT(7)
+#define TCU_JZ4730_TCSR_FLAG BIT(6)
+#define TCU_JZ4730_TCSR_EN BIT(5)
+
+#define TCU_JZ4730_CHANNEL_STRIDE 0x20
+#define TCU_JZ4730_REG_TRDRc(c) (TCU_JZ4730_REG_TRDR0 + ((c) * TCU_JZ4730_CHANNEL_STRIDE))
+#define TCU_JZ4730_REG_TCNTc(c) (TCU_JZ4730_REG_TCNT0 + ((c) * TCU_JZ4730_CHANNEL_STRIDE))
+#define TCU_JZ4730_REG_TCSRc(c) (TCU_JZ4730_REG_TCSR0 + ((c) * TCU_JZ4730_CHANNEL_STRIDE))
+
#endif /* __LINUX_MFD_INGENIC_TCU_H_ */
--
2.26.2
More information about the Letux-kernel
mailing list