xref: /linux/drivers/watchdog/meson_wdt.c (revision 353b7a55dcaf5fb8758e09ebe2ddf5f3adbac7c5)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
222e1b8f6SCarlo Caione /*
322e1b8f6SCarlo Caione  *      Meson Watchdog Driver
422e1b8f6SCarlo Caione  *
522e1b8f6SCarlo Caione  *      Copyright (c) 2014 Carlo Caione
622e1b8f6SCarlo Caione  */
722e1b8f6SCarlo Caione 
822e1b8f6SCarlo Caione #include <linux/clk.h>
922e1b8f6SCarlo Caione #include <linux/delay.h>
1022e1b8f6SCarlo Caione #include <linux/err.h>
1122e1b8f6SCarlo Caione #include <linux/init.h>
1222e1b8f6SCarlo Caione #include <linux/io.h>
1322e1b8f6SCarlo Caione #include <linux/kernel.h>
1422e1b8f6SCarlo Caione #include <linux/module.h>
1522e1b8f6SCarlo Caione #include <linux/moduleparam.h>
1622e1b8f6SCarlo Caione #include <linux/of.h>
17943bf1f6SCarlo Caione #include <linux/of_device.h>
1822e1b8f6SCarlo Caione #include <linux/platform_device.h>
1922e1b8f6SCarlo Caione #include <linux/types.h>
2022e1b8f6SCarlo Caione #include <linux/watchdog.h>
2122e1b8f6SCarlo Caione 
2222e1b8f6SCarlo Caione #define DRV_NAME		"meson_wdt"
2322e1b8f6SCarlo Caione 
2422e1b8f6SCarlo Caione #define MESON_WDT_TC		0x00
2522e1b8f6SCarlo Caione #define MESON_WDT_DC_RESET	(3 << 24)
2622e1b8f6SCarlo Caione 
2722e1b8f6SCarlo Caione #define MESON_WDT_RESET		0x04
2822e1b8f6SCarlo Caione 
2922e1b8f6SCarlo Caione #define MESON_WDT_TIMEOUT	30
3022e1b8f6SCarlo Caione #define MESON_WDT_MIN_TIMEOUT	1
3122e1b8f6SCarlo Caione 
32943bf1f6SCarlo Caione #define MESON_SEC_TO_TC(s, c)	((s) * (c))
3322e1b8f6SCarlo Caione 
3422e1b8f6SCarlo Caione static bool nowayout = WATCHDOG_NOWAYOUT;
354590d62cSMarcus Folkesson static unsigned int timeout;
3622e1b8f6SCarlo Caione 
37943bf1f6SCarlo Caione struct meson_wdt_data {
38943bf1f6SCarlo Caione 	unsigned int enable;
39943bf1f6SCarlo Caione 	unsigned int terminal_count_mask;
40943bf1f6SCarlo Caione 	unsigned int count_unit;
41943bf1f6SCarlo Caione };
42943bf1f6SCarlo Caione 
43943bf1f6SCarlo Caione static struct meson_wdt_data meson6_wdt_data = {
44943bf1f6SCarlo Caione 	.enable			= BIT(22),
45943bf1f6SCarlo Caione 	.terminal_count_mask	= 0x3fffff,
46943bf1f6SCarlo Caione 	.count_unit		= 100000, /* 10 us */
47943bf1f6SCarlo Caione };
48943bf1f6SCarlo Caione 
4971388840SCarlo Caione static struct meson_wdt_data meson8b_wdt_data = {
5071388840SCarlo Caione 	.enable			= BIT(19),
5171388840SCarlo Caione 	.terminal_count_mask	= 0xffff,
5271388840SCarlo Caione 	.count_unit		= 7812, /* 128 us */
5371388840SCarlo Caione };
5471388840SCarlo Caione 
5522e1b8f6SCarlo Caione struct meson_wdt_dev {
5622e1b8f6SCarlo Caione 	struct watchdog_device wdt_dev;
5722e1b8f6SCarlo Caione 	void __iomem *wdt_base;
58943bf1f6SCarlo Caione 	const struct meson_wdt_data *data;
5922e1b8f6SCarlo Caione };
6022e1b8f6SCarlo Caione 
614d8b229dSGuenter Roeck static int meson_wdt_restart(struct watchdog_device *wdt_dev,
624d8b229dSGuenter Roeck 			     unsigned long action, void *data)
6322e1b8f6SCarlo Caione {
641b6fd59aSDamien Riegel 	struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
65943bf1f6SCarlo Caione 	u32 tc_reboot = MESON_WDT_DC_RESET;
66943bf1f6SCarlo Caione 
67943bf1f6SCarlo Caione 	tc_reboot |= meson_wdt->data->enable;
6822e1b8f6SCarlo Caione 
6922e1b8f6SCarlo Caione 	while (1) {
7022e1b8f6SCarlo Caione 		writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC);
7122e1b8f6SCarlo Caione 		mdelay(5);
7222e1b8f6SCarlo Caione 	}
7322e1b8f6SCarlo Caione 
741b6fd59aSDamien Riegel 	return 0;
7522e1b8f6SCarlo Caione }
7622e1b8f6SCarlo Caione 
7722e1b8f6SCarlo Caione static int meson_wdt_ping(struct watchdog_device *wdt_dev)
7822e1b8f6SCarlo Caione {
7922e1b8f6SCarlo Caione 	struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
8022e1b8f6SCarlo Caione 
8122e1b8f6SCarlo Caione 	writel(0, meson_wdt->wdt_base + MESON_WDT_RESET);
8222e1b8f6SCarlo Caione 
8322e1b8f6SCarlo Caione 	return 0;
8422e1b8f6SCarlo Caione }
8522e1b8f6SCarlo Caione 
8622e1b8f6SCarlo Caione static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev,
8722e1b8f6SCarlo Caione 				     unsigned int timeout)
8822e1b8f6SCarlo Caione {
8922e1b8f6SCarlo Caione 	struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
9022e1b8f6SCarlo Caione 	u32 reg;
9122e1b8f6SCarlo Caione 
9222e1b8f6SCarlo Caione 	reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
93943bf1f6SCarlo Caione 	reg &= ~meson_wdt->data->terminal_count_mask;
94943bf1f6SCarlo Caione 	reg |= MESON_SEC_TO_TC(timeout, meson_wdt->data->count_unit);
9522e1b8f6SCarlo Caione 	writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
9622e1b8f6SCarlo Caione }
9722e1b8f6SCarlo Caione 
9822e1b8f6SCarlo Caione static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev,
9922e1b8f6SCarlo Caione 				 unsigned int timeout)
10022e1b8f6SCarlo Caione {
10122e1b8f6SCarlo Caione 	wdt_dev->timeout = timeout;
10222e1b8f6SCarlo Caione 
10322e1b8f6SCarlo Caione 	meson_wdt_change_timeout(wdt_dev, timeout);
10422e1b8f6SCarlo Caione 	meson_wdt_ping(wdt_dev);
10522e1b8f6SCarlo Caione 
10622e1b8f6SCarlo Caione 	return 0;
10722e1b8f6SCarlo Caione }
10822e1b8f6SCarlo Caione 
10922e1b8f6SCarlo Caione static int meson_wdt_stop(struct watchdog_device *wdt_dev)
11022e1b8f6SCarlo Caione {
11122e1b8f6SCarlo Caione 	struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
11222e1b8f6SCarlo Caione 	u32 reg;
11322e1b8f6SCarlo Caione 
11422e1b8f6SCarlo Caione 	reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
115943bf1f6SCarlo Caione 	reg &= ~meson_wdt->data->enable;
11622e1b8f6SCarlo Caione 	writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
11722e1b8f6SCarlo Caione 
11822e1b8f6SCarlo Caione 	return 0;
11922e1b8f6SCarlo Caione }
12022e1b8f6SCarlo Caione 
12122e1b8f6SCarlo Caione static int meson_wdt_start(struct watchdog_device *wdt_dev)
12222e1b8f6SCarlo Caione {
12322e1b8f6SCarlo Caione 	struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
12422e1b8f6SCarlo Caione 	u32 reg;
12522e1b8f6SCarlo Caione 
12622e1b8f6SCarlo Caione 	meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout);
12722e1b8f6SCarlo Caione 	meson_wdt_ping(wdt_dev);
12822e1b8f6SCarlo Caione 
12922e1b8f6SCarlo Caione 	reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
130943bf1f6SCarlo Caione 	reg |= meson_wdt->data->enable;
13122e1b8f6SCarlo Caione 	writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
13222e1b8f6SCarlo Caione 
13322e1b8f6SCarlo Caione 	return 0;
13422e1b8f6SCarlo Caione }
13522e1b8f6SCarlo Caione 
13622e1b8f6SCarlo Caione static const struct watchdog_info meson_wdt_info = {
13722e1b8f6SCarlo Caione 	.identity	= DRV_NAME,
13822e1b8f6SCarlo Caione 	.options	= WDIOF_SETTIMEOUT |
13922e1b8f6SCarlo Caione 			  WDIOF_KEEPALIVEPING |
14022e1b8f6SCarlo Caione 			  WDIOF_MAGICCLOSE,
14122e1b8f6SCarlo Caione };
14222e1b8f6SCarlo Caione 
14322e1b8f6SCarlo Caione static const struct watchdog_ops meson_wdt_ops = {
14422e1b8f6SCarlo Caione 	.owner		= THIS_MODULE,
14522e1b8f6SCarlo Caione 	.start		= meson_wdt_start,
14622e1b8f6SCarlo Caione 	.stop		= meson_wdt_stop,
14722e1b8f6SCarlo Caione 	.ping		= meson_wdt_ping,
14822e1b8f6SCarlo Caione 	.set_timeout	= meson_wdt_set_timeout,
1491b6fd59aSDamien Riegel 	.restart        = meson_wdt_restart,
15022e1b8f6SCarlo Caione };
15122e1b8f6SCarlo Caione 
152943bf1f6SCarlo Caione static const struct of_device_id meson_wdt_dt_ids[] = {
153943bf1f6SCarlo Caione 	{ .compatible = "amlogic,meson6-wdt", .data = &meson6_wdt_data },
15443a64e81SMartin Blumenstingl 	{ .compatible = "amlogic,meson8-wdt", .data = &meson6_wdt_data },
15571388840SCarlo Caione 	{ .compatible = "amlogic,meson8b-wdt", .data = &meson8b_wdt_data },
15643a64e81SMartin Blumenstingl 	{ .compatible = "amlogic,meson8m2-wdt", .data = &meson8b_wdt_data },
157943bf1f6SCarlo Caione 	{ /* sentinel */ }
158943bf1f6SCarlo Caione };
159943bf1f6SCarlo Caione MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids);
160943bf1f6SCarlo Caione 
16122e1b8f6SCarlo Caione static int meson_wdt_probe(struct platform_device *pdev)
16222e1b8f6SCarlo Caione {
163dd1c66e2SGuenter Roeck 	struct device *dev = &pdev->dev;
16422e1b8f6SCarlo Caione 	struct meson_wdt_dev *meson_wdt;
16522e1b8f6SCarlo Caione 	int err;
16622e1b8f6SCarlo Caione 
167dd1c66e2SGuenter Roeck 	meson_wdt = devm_kzalloc(dev, sizeof(*meson_wdt), GFP_KERNEL);
16822e1b8f6SCarlo Caione 	if (!meson_wdt)
16922e1b8f6SCarlo Caione 		return -ENOMEM;
17022e1b8f6SCarlo Caione 
1710f0a6a28SGuenter Roeck 	meson_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
17222e1b8f6SCarlo Caione 	if (IS_ERR(meson_wdt->wdt_base))
17322e1b8f6SCarlo Caione 		return PTR_ERR(meson_wdt->wdt_base);
17422e1b8f6SCarlo Caione 
175*0a1186e4STian Tao 	meson_wdt->data = device_get_match_data(dev);
176943bf1f6SCarlo Caione 
177dd1c66e2SGuenter Roeck 	meson_wdt->wdt_dev.parent = dev;
17822e1b8f6SCarlo Caione 	meson_wdt->wdt_dev.info = &meson_wdt_info;
17922e1b8f6SCarlo Caione 	meson_wdt->wdt_dev.ops = &meson_wdt_ops;
180943bf1f6SCarlo Caione 	meson_wdt->wdt_dev.max_timeout =
181943bf1f6SCarlo Caione 		meson_wdt->data->terminal_count_mask / meson_wdt->data->count_unit;
18222e1b8f6SCarlo Caione 	meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT;
183943bf1f6SCarlo Caione 	meson_wdt->wdt_dev.timeout = min_t(unsigned int,
184943bf1f6SCarlo Caione 					   MESON_WDT_TIMEOUT,
185943bf1f6SCarlo Caione 					   meson_wdt->wdt_dev.max_timeout);
18622e1b8f6SCarlo Caione 
18722e1b8f6SCarlo Caione 	watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt);
18822e1b8f6SCarlo Caione 
189dd1c66e2SGuenter Roeck 	watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, dev);
19022e1b8f6SCarlo Caione 	watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout);
1911b6fd59aSDamien Riegel 	watchdog_set_restart_priority(&meson_wdt->wdt_dev, 128);
19222e1b8f6SCarlo Caione 
19322e1b8f6SCarlo Caione 	meson_wdt_stop(&meson_wdt->wdt_dev);
19422e1b8f6SCarlo Caione 
195c8841a60SGuenter Roeck 	watchdog_stop_on_reboot(&meson_wdt->wdt_dev);
196dd1c66e2SGuenter Roeck 	err = devm_watchdog_register_device(dev, &meson_wdt->wdt_dev);
19722e1b8f6SCarlo Caione 	if (err)
19822e1b8f6SCarlo Caione 		return err;
19922e1b8f6SCarlo Caione 
200dd1c66e2SGuenter Roeck 	dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
20122e1b8f6SCarlo Caione 		 meson_wdt->wdt_dev.timeout, nowayout);
20222e1b8f6SCarlo Caione 
20322e1b8f6SCarlo Caione 	return 0;
20422e1b8f6SCarlo Caione }
20522e1b8f6SCarlo Caione 
20622e1b8f6SCarlo Caione static struct platform_driver meson_wdt_driver = {
20722e1b8f6SCarlo Caione 	.probe		= meson_wdt_probe,
20822e1b8f6SCarlo Caione 	.driver		= {
20922e1b8f6SCarlo Caione 		.name		= DRV_NAME,
21022e1b8f6SCarlo Caione 		.of_match_table	= meson_wdt_dt_ids,
21122e1b8f6SCarlo Caione 	},
21222e1b8f6SCarlo Caione };
21322e1b8f6SCarlo Caione 
21422e1b8f6SCarlo Caione module_platform_driver(meson_wdt_driver);
21522e1b8f6SCarlo Caione 
21622e1b8f6SCarlo Caione module_param(timeout, uint, 0);
21722e1b8f6SCarlo Caione MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
21822e1b8f6SCarlo Caione 
21922e1b8f6SCarlo Caione module_param(nowayout, bool, 0);
22022e1b8f6SCarlo Caione MODULE_PARM_DESC(nowayout,
22122e1b8f6SCarlo Caione 		 "Watchdog cannot be stopped once started (default="
22222e1b8f6SCarlo Caione 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
22322e1b8f6SCarlo Caione 
22422e1b8f6SCarlo Caione MODULE_LICENSE("GPL");
22522e1b8f6SCarlo Caione MODULE_AUTHOR("Carlo Caione <carlo@caione.org>");
22622e1b8f6SCarlo Caione MODULE_DESCRIPTION("Meson Watchdog Timer Driver");
227