[Letux-kernel] [RFC 19/28] watchdog: jz4740: add JZ4730 watchdog timer support
H. Nikolaus Schaller
hns at goldelico.com
Sat Jan 23 17:28:45 CET 2021
From: Paul Boddie <paul at boddie.org.uk>
The clock arrangement for the JZ4730 differs from other Ingenic
SoCs, with a multiplexer between EXCLK/128 and the real-time clock
feeding the watchdog timer. Meanwhile, the watchdog registers are
separate from the timer/counter unit.
Thus, the JZ4740 watchdog driver has been parameterised to allow
different registers and different operations to be used, depending
on the SoC involved.
Signed-off-by: Paul Boddie <paul at boddie.org.uk>
Signed-off-by: H. Nikolaus Schaller <hns at goldelico.com>
---
drivers/watchdog/jz4740_wdt.c | 104 +++++++++++++++++++++++++++++-----
1 file changed, 89 insertions(+), 15 deletions(-)
diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c
index bdf9564efa29e..cb144d2335db5 100644
--- a/drivers/watchdog/jz4740_wdt.c
+++ b/drivers/watchdog/jz4740_wdt.c
@@ -2,6 +2,9 @@
/*
* Copyright (C) 2010, Paul Cercueil <paul at crapouillou.net>
* JZ4740 Watchdog driver
+ *
+ * Copyright (C) 2017, 2020, Paul Boddie <paul at boddie.org.uk>
+ * JZ4730 customisations
*/
#include <linux/mfd/ingenic-tcu.h>
@@ -18,6 +21,7 @@
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/regmap.h>
#define DEFAULT_HEARTBEAT 5
@@ -36,37 +40,72 @@ MODULE_PARM_DESC(heartbeat,
__MODULE_STRING(MAX_HEARTBEAT) ", default "
__MODULE_STRING(DEFAULT_HEARTBEAT));
+struct jz_wdt_soc_info {
+ const struct watchdog_ops *ops;
+ unsigned int counter;
+ unsigned int enable;
+ unsigned int enable_start;
+};
+
struct jz4740_wdt_drvdata {
struct watchdog_device wdt;
struct regmap *map;
struct clk *clk;
unsigned long clk_rate;
+ const struct jz_wdt_soc_info *soc_info;
};
static int jz4740_wdt_ping(struct watchdog_device *wdt_dev)
{
struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
- regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
+ regmap_write(drvdata->map, drvdata->soc_info->counter, 0);
+
+ return 0;
+}
+
+static int jz4730_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int new_timeout)
+{
+ struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+ const struct jz_wdt_soc_info *soc_info = drvdata->soc_info;
+ u16 timeout_value;
+ unsigned int tcer;
+
+ regmap_read(drvdata->map, soc_info->enable, &tcer);
+ regmap_write(drvdata->map, soc_info->enable, 0);
+
+ /* On the JZ4730, the timer limit raises the alarm, and so the timeout
+ * must be subtracted from the limit to produce a starting value.
+ */
+
+ timeout_value = 0xffffffff - (u16)(drvdata->clk_rate * new_timeout);
+ regmap_write(drvdata->map, soc_info->counter, timeout_value);
+
+ if (tcer & soc_info->enable_start)
+ regmap_write(drvdata->map, soc_info->enable, soc_info->enable_start);
+
+ wdt_dev->timeout = new_timeout;
return 0;
}
static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev,
- unsigned int new_timeout)
+ unsigned int new_timeout)
{
struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+ const struct jz_wdt_soc_info *soc_info = drvdata->soc_info;
u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout);
unsigned int tcer;
- regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer);
- regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
+ regmap_read(drvdata->map, soc_info->enable, &tcer);
+ regmap_write(drvdata->map, soc_info->enable, 0);
regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value);
- regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
+ regmap_write(drvdata->map, soc_info->counter, 0);
- if (tcer & TCU_WDT_TCER_TCEN)
- regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
+ if (tcer & soc_info->enable_start)
+ regmap_write(drvdata->map, soc_info->enable, soc_info->enable_start);
wdt_dev->timeout = new_timeout;
return 0;
@@ -75,6 +114,7 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev,
static int jz4740_wdt_start(struct watchdog_device *wdt_dev)
{
struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+ const struct jz_wdt_soc_info *soc_info = drvdata->soc_info;
unsigned int tcer;
int ret;
@@ -82,13 +122,13 @@ static int jz4740_wdt_start(struct watchdog_device *wdt_dev)
if (ret)
return ret;
- regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer);
+ regmap_read(drvdata->map, soc_info->enable, &tcer);
jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
/* Start watchdog if it wasn't started already */
- if (!(tcer & TCU_WDT_TCER_TCEN))
- regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
+ if (!(tcer & soc_info->enable_start))
+ regmap_write(drvdata->map, soc_info->enable, soc_info->enable_start);
return 0;
}
@@ -96,8 +136,9 @@ static int jz4740_wdt_start(struct watchdog_device *wdt_dev)
static int jz4740_wdt_stop(struct watchdog_device *wdt_dev)
{
struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+ const struct jz_wdt_soc_info *soc_info = drvdata->soc_info;
- regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
+ regmap_write(drvdata->map, soc_info->enable, 0);
clk_disable_unprepare(drvdata->clk);
return 0;
@@ -106,8 +147,10 @@ static int jz4740_wdt_stop(struct watchdog_device *wdt_dev)
static int jz4740_wdt_restart(struct watchdog_device *wdt_dev,
unsigned long action, void *data)
{
+ struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+
wdt_dev->timeout = 0;
- jz4740_wdt_start(wdt_dev);
+ drvdata->soc_info->ops->start(wdt_dev);
return 0;
}
@@ -116,6 +159,15 @@ static const struct watchdog_info jz4740_wdt_info = {
.identity = "jz4740 Watchdog",
};
+static const struct watchdog_ops jz4730_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = jz4740_wdt_start,
+ .stop = jz4740_wdt_stop,
+ .ping = jz4740_wdt_ping,
+ .set_timeout = jz4730_wdt_set_timeout,
+ .restart = jz4740_wdt_restart,
+};
+
static const struct watchdog_ops jz4740_wdt_ops = {
.owner = THIS_MODULE,
.start = jz4740_wdt_start,
@@ -125,10 +177,25 @@ static const struct watchdog_ops jz4740_wdt_ops = {
.restart = jz4740_wdt_restart,
};
+static const struct jz_wdt_soc_info jz4730_wdt_soc_info = {
+ .ops = &jz4730_wdt_ops,
+ .counter = WDT_JZ4730_REG_TCNT,
+ .enable = WDT_JZ4730_REG_TCSR,
+ .enable_start = WDT_JZ4730_TCSR_EN,
+};
+
+static const struct jz_wdt_soc_info jz4740_wdt_soc_info = {
+ .ops = &jz4740_wdt_ops,
+ .counter = TCU_REG_WDT_TCNT,
+ .enable = TCU_REG_WDT_TCER,
+ .enable_start = TCU_WDT_TCER_TCEN,
+};
+
#ifdef CONFIG_OF
static const struct of_device_id jz4740_wdt_of_matches[] = {
- { .compatible = "ingenic,jz4740-watchdog", },
- { .compatible = "ingenic,jz4780-watchdog", },
+ { .compatible = "ingenic,jz4730-watchdog", .data = &jz4730_wdt_soc_info },
+ { .compatible = "ingenic,jz4740-watchdog", .data = &jz4740_wdt_soc_info },
+ { .compatible = "ingenic,jz4780-watchdog", .data = &jz4740_wdt_soc_info },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches);
@@ -136,12 +203,19 @@ MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches);
static int jz4740_wdt_probe(struct platform_device *pdev)
{
+ const struct jz_wdt_soc_info *soc_info;
struct device *dev = &pdev->dev;
struct jz4740_wdt_drvdata *drvdata;
struct watchdog_device *jz4740_wdt;
long rate;
int ret;
+ soc_info = of_device_get_match_data(dev);
+ if (!soc_info) {
+ dev_err(dev, "Missing platform data\n");
+ return -EINVAL;
+ }
+
drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata),
GFP_KERNEL);
if (!drvdata)
@@ -165,7 +239,7 @@ static int jz4740_wdt_probe(struct platform_device *pdev)
drvdata->clk_rate = rate;
jz4740_wdt = &drvdata->wdt;
jz4740_wdt->info = &jz4740_wdt_info;
- jz4740_wdt->ops = &jz4740_wdt_ops;
+ jz4740_wdt->ops = soc_info->ops;
jz4740_wdt->min_timeout = 1;
jz4740_wdt->max_timeout = 0xffff / rate;
jz4740_wdt->timeout = clamp(heartbeat,
--
2.26.2
More information about the Letux-kernel
mailing list