xref: /linux/drivers/watchdog/meson_gxbb_wdt.c (revision c95baf12f5077419db01313ab61c2aac007d40cd)
12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2683fa50fSNeil Armstrong /*
3683fa50fSNeil Armstrong  * Copyright (c) 2016 BayLibre, SAS.
4683fa50fSNeil Armstrong  * Author: Neil Armstrong <narmstrong@baylibre.com>
5683fa50fSNeil Armstrong  *
6683fa50fSNeil Armstrong  */
7683fa50fSNeil Armstrong #include <linux/clk.h>
8683fa50fSNeil Armstrong #include <linux/err.h>
9683fa50fSNeil Armstrong #include <linux/io.h>
10683fa50fSNeil Armstrong #include <linux/module.h>
11683fa50fSNeil Armstrong #include <linux/of.h>
12683fa50fSNeil Armstrong #include <linux/platform_device.h>
13683fa50fSNeil Armstrong #include <linux/slab.h>
14683fa50fSNeil Armstrong #include <linux/types.h>
15683fa50fSNeil Armstrong #include <linux/watchdog.h>
16683fa50fSNeil Armstrong 
17683fa50fSNeil Armstrong #define DEFAULT_TIMEOUT	30	/* seconds */
18683fa50fSNeil Armstrong 
19683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_REG			0x0
20683fa50fSNeil Armstrong #define GXBB_WDT_TCNT_REG			0x8
21683fa50fSNeil Armstrong #define GXBB_WDT_RSET_REG			0xc
22683fa50fSNeil Armstrong 
23683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_CLKDIV_EN			BIT(25)
24683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_CLK_EN			BIT(24)
25683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_EE_RESET			BIT(21)
26683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_EN			BIT(18)
27683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_DIV_MASK			(BIT(18) - 1)
28683fa50fSNeil Armstrong 
29683fa50fSNeil Armstrong #define GXBB_WDT_TCNT_SETUP_MASK		(BIT(16) - 1)
30683fa50fSNeil Armstrong #define GXBB_WDT_TCNT_CNT_SHIFT			16
31683fa50fSNeil Armstrong 
32683fa50fSNeil Armstrong struct meson_gxbb_wdt {
33683fa50fSNeil Armstrong 	void __iomem *reg_base;
34683fa50fSNeil Armstrong 	struct watchdog_device wdt_dev;
35683fa50fSNeil Armstrong 	struct clk *clk;
36683fa50fSNeil Armstrong };
37683fa50fSNeil Armstrong 
38683fa50fSNeil Armstrong static int meson_gxbb_wdt_start(struct watchdog_device *wdt_dev)
39683fa50fSNeil Armstrong {
40683fa50fSNeil Armstrong 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
41683fa50fSNeil Armstrong 
42683fa50fSNeil Armstrong 	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN,
43683fa50fSNeil Armstrong 	       data->reg_base + GXBB_WDT_CTRL_REG);
44683fa50fSNeil Armstrong 
45683fa50fSNeil Armstrong 	return 0;
46683fa50fSNeil Armstrong }
47683fa50fSNeil Armstrong 
48683fa50fSNeil Armstrong static int meson_gxbb_wdt_stop(struct watchdog_device *wdt_dev)
49683fa50fSNeil Armstrong {
50683fa50fSNeil Armstrong 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
51683fa50fSNeil Armstrong 
52683fa50fSNeil Armstrong 	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN,
53683fa50fSNeil Armstrong 	       data->reg_base + GXBB_WDT_CTRL_REG);
54683fa50fSNeil Armstrong 
55683fa50fSNeil Armstrong 	return 0;
56683fa50fSNeil Armstrong }
57683fa50fSNeil Armstrong 
58683fa50fSNeil Armstrong static int meson_gxbb_wdt_ping(struct watchdog_device *wdt_dev)
59683fa50fSNeil Armstrong {
60683fa50fSNeil Armstrong 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
61683fa50fSNeil Armstrong 
62683fa50fSNeil Armstrong 	writel(0, data->reg_base + GXBB_WDT_RSET_REG);
63683fa50fSNeil Armstrong 
64683fa50fSNeil Armstrong 	return 0;
65683fa50fSNeil Armstrong }
66683fa50fSNeil Armstrong 
67683fa50fSNeil Armstrong static int meson_gxbb_wdt_set_timeout(struct watchdog_device *wdt_dev,
68683fa50fSNeil Armstrong 				      unsigned int timeout)
69683fa50fSNeil Armstrong {
70683fa50fSNeil Armstrong 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
71683fa50fSNeil Armstrong 	unsigned long tcnt = timeout * 1000;
72683fa50fSNeil Armstrong 
73683fa50fSNeil Armstrong 	if (tcnt > GXBB_WDT_TCNT_SETUP_MASK)
74683fa50fSNeil Armstrong 		tcnt = GXBB_WDT_TCNT_SETUP_MASK;
75683fa50fSNeil Armstrong 
76683fa50fSNeil Armstrong 	wdt_dev->timeout = timeout;
77683fa50fSNeil Armstrong 
78683fa50fSNeil Armstrong 	meson_gxbb_wdt_ping(wdt_dev);
79683fa50fSNeil Armstrong 
80683fa50fSNeil Armstrong 	writel(tcnt, data->reg_base + GXBB_WDT_TCNT_REG);
81683fa50fSNeil Armstrong 
82683fa50fSNeil Armstrong 	return 0;
83683fa50fSNeil Armstrong }
84683fa50fSNeil Armstrong 
85683fa50fSNeil Armstrong static unsigned int meson_gxbb_wdt_get_timeleft(struct watchdog_device *wdt_dev)
86683fa50fSNeil Armstrong {
87683fa50fSNeil Armstrong 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
88683fa50fSNeil Armstrong 	unsigned long reg;
89683fa50fSNeil Armstrong 
90683fa50fSNeil Armstrong 	reg = readl(data->reg_base + GXBB_WDT_TCNT_REG);
91683fa50fSNeil Armstrong 
92*2c777346SXingyu Chen 	return ((reg & GXBB_WDT_TCNT_SETUP_MASK) -
93*2c777346SXingyu Chen 		(reg >> GXBB_WDT_TCNT_CNT_SHIFT)) / 1000;
94683fa50fSNeil Armstrong }
95683fa50fSNeil Armstrong 
96683fa50fSNeil Armstrong static const struct watchdog_ops meson_gxbb_wdt_ops = {
97683fa50fSNeil Armstrong 	.start = meson_gxbb_wdt_start,
98683fa50fSNeil Armstrong 	.stop = meson_gxbb_wdt_stop,
99683fa50fSNeil Armstrong 	.ping = meson_gxbb_wdt_ping,
100683fa50fSNeil Armstrong 	.set_timeout = meson_gxbb_wdt_set_timeout,
101683fa50fSNeil Armstrong 	.get_timeleft = meson_gxbb_wdt_get_timeleft,
102683fa50fSNeil Armstrong };
103683fa50fSNeil Armstrong 
104683fa50fSNeil Armstrong static const struct watchdog_info meson_gxbb_wdt_info = {
105683fa50fSNeil Armstrong 	.identity = "Meson GXBB Watchdog",
106683fa50fSNeil Armstrong 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
107683fa50fSNeil Armstrong };
108683fa50fSNeil Armstrong 
109683fa50fSNeil Armstrong static int __maybe_unused meson_gxbb_wdt_resume(struct device *dev)
110683fa50fSNeil Armstrong {
111683fa50fSNeil Armstrong 	struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
112683fa50fSNeil Armstrong 
113683fa50fSNeil Armstrong 	if (watchdog_active(&data->wdt_dev))
114683fa50fSNeil Armstrong 		meson_gxbb_wdt_start(&data->wdt_dev);
115683fa50fSNeil Armstrong 
116683fa50fSNeil Armstrong 	return 0;
117683fa50fSNeil Armstrong }
118683fa50fSNeil Armstrong 
119683fa50fSNeil Armstrong static int __maybe_unused meson_gxbb_wdt_suspend(struct device *dev)
120683fa50fSNeil Armstrong {
121683fa50fSNeil Armstrong 	struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
122683fa50fSNeil Armstrong 
123683fa50fSNeil Armstrong 	if (watchdog_active(&data->wdt_dev))
124683fa50fSNeil Armstrong 		meson_gxbb_wdt_stop(&data->wdt_dev);
125683fa50fSNeil Armstrong 
126683fa50fSNeil Armstrong 	return 0;
127683fa50fSNeil Armstrong }
128683fa50fSNeil Armstrong 
129683fa50fSNeil Armstrong static const struct dev_pm_ops meson_gxbb_wdt_pm_ops = {
130683fa50fSNeil Armstrong 	SET_SYSTEM_SLEEP_PM_OPS(meson_gxbb_wdt_suspend, meson_gxbb_wdt_resume)
131683fa50fSNeil Armstrong };
132683fa50fSNeil Armstrong 
133683fa50fSNeil Armstrong static const struct of_device_id meson_gxbb_wdt_dt_ids[] = {
134683fa50fSNeil Armstrong 	 { .compatible = "amlogic,meson-gxbb-wdt", },
135683fa50fSNeil Armstrong 	 { /* sentinel */ },
136683fa50fSNeil Armstrong };
137683fa50fSNeil Armstrong MODULE_DEVICE_TABLE(of, meson_gxbb_wdt_dt_ids);
138683fa50fSNeil Armstrong 
1391678f830SGuenter Roeck static void meson_clk_disable_unprepare(void *data)
1401678f830SGuenter Roeck {
1411678f830SGuenter Roeck 	clk_disable_unprepare(data);
1421678f830SGuenter Roeck }
1431678f830SGuenter Roeck 
144683fa50fSNeil Armstrong static int meson_gxbb_wdt_probe(struct platform_device *pdev)
145683fa50fSNeil Armstrong {
1461678f830SGuenter Roeck 	struct device *dev = &pdev->dev;
147683fa50fSNeil Armstrong 	struct meson_gxbb_wdt *data;
148683fa50fSNeil Armstrong 	int ret;
149683fa50fSNeil Armstrong 
1501678f830SGuenter Roeck 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
151683fa50fSNeil Armstrong 	if (!data)
152683fa50fSNeil Armstrong 		return -ENOMEM;
153683fa50fSNeil Armstrong 
1540f0a6a28SGuenter Roeck 	data->reg_base = devm_platform_ioremap_resource(pdev, 0);
155683fa50fSNeil Armstrong 	if (IS_ERR(data->reg_base))
156683fa50fSNeil Armstrong 		return PTR_ERR(data->reg_base);
157683fa50fSNeil Armstrong 
1581678f830SGuenter Roeck 	data->clk = devm_clk_get(dev, NULL);
159683fa50fSNeil Armstrong 	if (IS_ERR(data->clk))
160683fa50fSNeil Armstrong 		return PTR_ERR(data->clk);
161683fa50fSNeil Armstrong 
16265360944SArvind Yadav 	ret = clk_prepare_enable(data->clk);
16365360944SArvind Yadav 	if (ret)
16465360944SArvind Yadav 		return ret;
1651678f830SGuenter Roeck 	ret = devm_add_action_or_reset(dev, meson_clk_disable_unprepare,
1661678f830SGuenter Roeck 				       data->clk);
1671678f830SGuenter Roeck 	if (ret)
1681678f830SGuenter Roeck 		return ret;
169683fa50fSNeil Armstrong 
170683fa50fSNeil Armstrong 	platform_set_drvdata(pdev, data);
171683fa50fSNeil Armstrong 
1721678f830SGuenter Roeck 	data->wdt_dev.parent = dev;
173683fa50fSNeil Armstrong 	data->wdt_dev.info = &meson_gxbb_wdt_info;
174683fa50fSNeil Armstrong 	data->wdt_dev.ops = &meson_gxbb_wdt_ops;
175683fa50fSNeil Armstrong 	data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK;
176683fa50fSNeil Armstrong 	data->wdt_dev.min_timeout = 1;
177683fa50fSNeil Armstrong 	data->wdt_dev.timeout = DEFAULT_TIMEOUT;
178683fa50fSNeil Armstrong 	watchdog_set_drvdata(&data->wdt_dev, data);
179683fa50fSNeil Armstrong 
180683fa50fSNeil Armstrong 	/* Setup with 1ms timebase */
181683fa50fSNeil Armstrong 	writel(((clk_get_rate(data->clk) / 1000) & GXBB_WDT_CTRL_DIV_MASK) |
182683fa50fSNeil Armstrong 		GXBB_WDT_CTRL_EE_RESET |
183683fa50fSNeil Armstrong 		GXBB_WDT_CTRL_CLK_EN |
184683fa50fSNeil Armstrong 		GXBB_WDT_CTRL_CLKDIV_EN,
185683fa50fSNeil Armstrong 		data->reg_base + GXBB_WDT_CTRL_REG);
186683fa50fSNeil Armstrong 
187683fa50fSNeil Armstrong 	meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
188683fa50fSNeil Armstrong 
1891678f830SGuenter Roeck 	watchdog_stop_on_reboot(&data->wdt_dev);
1901678f830SGuenter Roeck 	return devm_watchdog_register_device(dev, &data->wdt_dev);
191683fa50fSNeil Armstrong }
192683fa50fSNeil Armstrong 
193683fa50fSNeil Armstrong static struct platform_driver meson_gxbb_wdt_driver = {
194683fa50fSNeil Armstrong 	.probe	= meson_gxbb_wdt_probe,
195683fa50fSNeil Armstrong 	.driver = {
196683fa50fSNeil Armstrong 		.name = "meson-gxbb-wdt",
197683fa50fSNeil Armstrong 		.pm = &meson_gxbb_wdt_pm_ops,
198683fa50fSNeil Armstrong 		.of_match_table	= meson_gxbb_wdt_dt_ids,
199683fa50fSNeil Armstrong 	},
200683fa50fSNeil Armstrong };
201683fa50fSNeil Armstrong 
202683fa50fSNeil Armstrong module_platform_driver(meson_gxbb_wdt_driver);
203683fa50fSNeil Armstrong 
204683fa50fSNeil Armstrong MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
205683fa50fSNeil Armstrong MODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver");
206683fa50fSNeil Armstrong MODULE_LICENSE("Dual BSD/GPL");
207