1258945Sroberto// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2258945Sroberto/*
3258945Sroberto * Copyright (c) 2016 BayLibre, SAS.
4258945Sroberto * Author: Neil Armstrong <narmstrong@baylibre.com>
5258945Sroberto *
6258945Sroberto */
7258945Sroberto#include <linux/clk.h>
8258945Sroberto#include <linux/err.h>
9258945Sroberto#include <linux/io.h>
10258945Sroberto#include <linux/module.h>
11258945Sroberto#include <linux/of.h>
12258945Sroberto#include <linux/platform_device.h>
13258945Sroberto#include <linux/slab.h>
14258945Sroberto#include <linux/types.h>
15258945Sroberto#include <linux/watchdog.h>
16258945Sroberto
17258945Sroberto#define DEFAULT_TIMEOUT	30	/* seconds */
18258945Sroberto
19258945Sroberto#define GXBB_WDT_CTRL_REG			0x0
20258945Sroberto#define GXBB_WDT_TCNT_REG			0x8
21258945Sroberto#define GXBB_WDT_RSET_REG			0xc
22258945Sroberto
23258945Sroberto#define GXBB_WDT_CTRL_CLKDIV_EN			BIT(25)
24258945Sroberto#define GXBB_WDT_CTRL_CLK_EN			BIT(24)
25258945Sroberto#define GXBB_WDT_CTRL_EN			BIT(18)
26258945Sroberto#define GXBB_WDT_CTRL_DIV_MASK			(BIT(18) - 1)
27258945Sroberto
28258945Sroberto#define GXBB_WDT_TCNT_SETUP_MASK		(BIT(16) - 1)
29258945Sroberto#define GXBB_WDT_TCNT_CNT_SHIFT			16
30258945Sroberto
31258945Srobertostatic bool nowayout = WATCHDOG_NOWAYOUT;
32258945Srobertomodule_param(nowayout, bool, 0);
33258945SrobertoMODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started default="
34258945Sroberto		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
35258945Sroberto
36258945Srobertostatic unsigned int timeout;
37258945Srobertomodule_param(timeout, uint, 0);
38258945SrobertoMODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds="
39258945Sroberto		 __MODULE_STRING(DEFAULT_TIMEOUT) ")");
40258945Sroberto
41258945Srobertostruct meson_gxbb_wdt {
42258945Sroberto	void __iomem *reg_base;
43258945Sroberto	struct watchdog_device wdt_dev;
44258945Sroberto	struct clk *clk;
45258945Sroberto};
46258945Sroberto
47258945Srobertostruct wdt_params {
48258945Sroberto	u32 rst;
49258945Sroberto};
50258945Sroberto
51258945Srobertostatic int meson_gxbb_wdt_start(struct watchdog_device *wdt_dev)
52258945Sroberto{
53258945Sroberto	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
54258945Sroberto
55258945Sroberto	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN,
56258945Sroberto	       data->reg_base + GXBB_WDT_CTRL_REG);
57258945Sroberto
58258945Sroberto	return 0;
59258945Sroberto}
60258945Sroberto
61258945Srobertostatic int meson_gxbb_wdt_stop(struct watchdog_device *wdt_dev)
62258945Sroberto{
63258945Sroberto	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
64258945Sroberto
65258945Sroberto	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN,
66258945Sroberto	       data->reg_base + GXBB_WDT_CTRL_REG);
67258945Sroberto
68258945Sroberto	return 0;
69258945Sroberto}
70258945Sroberto
71258945Srobertostatic int meson_gxbb_wdt_ping(struct watchdog_device *wdt_dev)
72258945Sroberto{
73258945Sroberto	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
74258945Sroberto
75258945Sroberto	writel(0, data->reg_base + GXBB_WDT_RSET_REG);
76258945Sroberto
77258945Sroberto	return 0;
78258945Sroberto}
79258945Sroberto
80258945Srobertostatic int meson_gxbb_wdt_set_timeout(struct watchdog_device *wdt_dev,
81258945Sroberto				      unsigned int timeout)
82258945Sroberto{
83258945Sroberto	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
84258945Sroberto	unsigned long tcnt = timeout * 1000;
85258945Sroberto
86258945Sroberto	if (tcnt > GXBB_WDT_TCNT_SETUP_MASK)
87258945Sroberto		tcnt = GXBB_WDT_TCNT_SETUP_MASK;
88258945Sroberto
89258945Sroberto	wdt_dev->timeout = timeout;
90258945Sroberto
91258945Sroberto	meson_gxbb_wdt_ping(wdt_dev);
92258945Sroberto
93258945Sroberto	writel(tcnt, data->reg_base + GXBB_WDT_TCNT_REG);
94258945Sroberto
95258945Sroberto	return 0;
96258945Sroberto}
97258945Sroberto
98258945Srobertostatic unsigned int meson_gxbb_wdt_get_timeleft(struct watchdog_device *wdt_dev)
99258945Sroberto{
100258945Sroberto	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
101258945Sroberto	unsigned long reg;
102258945Sroberto
103258945Sroberto	reg = readl(data->reg_base + GXBB_WDT_TCNT_REG);
104258945Sroberto
105258945Sroberto	return ((reg & GXBB_WDT_TCNT_SETUP_MASK) -
106258945Sroberto		(reg >> GXBB_WDT_TCNT_CNT_SHIFT)) / 1000;
107258945Sroberto}
108258945Sroberto
109258945Srobertostatic const struct watchdog_ops meson_gxbb_wdt_ops = {
110258945Sroberto	.start = meson_gxbb_wdt_start,
111258945Sroberto	.stop = meson_gxbb_wdt_stop,
112258945Sroberto	.ping = meson_gxbb_wdt_ping,
113258945Sroberto	.set_timeout = meson_gxbb_wdt_set_timeout,
114258945Sroberto	.get_timeleft = meson_gxbb_wdt_get_timeleft,
115258945Sroberto};
116258945Sroberto
117258945Srobertostatic const struct watchdog_info meson_gxbb_wdt_info = {
118258945Sroberto	.identity = "Meson GXBB Watchdog",
119258945Sroberto	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
120258945Sroberto};
121258945Sroberto
122258945Srobertostatic int __maybe_unused meson_gxbb_wdt_resume(struct device *dev)
123258945Sroberto{
124258945Sroberto	struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
125258945Sroberto
126258945Sroberto	if (watchdog_active(&data->wdt_dev))
127258945Sroberto		meson_gxbb_wdt_start(&data->wdt_dev);
128258945Sroberto
129258945Sroberto	return 0;
130258945Sroberto}
131258945Sroberto
132258945Srobertostatic int __maybe_unused meson_gxbb_wdt_suspend(struct device *dev)
133258945Sroberto{
134258945Sroberto	struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
135258945Sroberto
136258945Sroberto	if (watchdog_active(&data->wdt_dev))
137258945Sroberto		meson_gxbb_wdt_stop(&data->wdt_dev);
138258945Sroberto
139258945Sroberto	return 0;
140258945Sroberto}
141258945Sroberto
142258945Srobertostatic const struct dev_pm_ops meson_gxbb_wdt_pm_ops = {
143258945Sroberto	SET_SYSTEM_SLEEP_PM_OPS(meson_gxbb_wdt_suspend, meson_gxbb_wdt_resume)
144258945Sroberto};
145258945Sroberto
146258945Srobertostatic const struct wdt_params gxbb_params = {
147258945Sroberto	.rst = BIT(21),
148258945Sroberto};
149258945Sroberto
150258945Srobertostatic const struct wdt_params t7_params = {
151258945Sroberto	.rst = BIT(22),
152258945Sroberto};
153258945Sroberto
154258945Srobertostatic const struct of_device_id meson_gxbb_wdt_dt_ids[] = {
155258945Sroberto	 { .compatible = "amlogic,meson-gxbb-wdt", .data = &gxbb_params, },
156258945Sroberto	 { .compatible = "amlogic,t7-wdt", .data = &t7_params, },
157258945Sroberto	 { /* sentinel */ },
158258945Sroberto};
159258945SrobertoMODULE_DEVICE_TABLE(of, meson_gxbb_wdt_dt_ids);
160258945Sroberto
161258945Srobertostatic int meson_gxbb_wdt_probe(struct platform_device *pdev)
162258945Sroberto{
163258945Sroberto	struct device *dev = &pdev->dev;
164258945Sroberto	struct meson_gxbb_wdt *data;
165258945Sroberto	struct wdt_params *params;
166258945Sroberto	u32 ctrl_reg;
167258945Sroberto
168258945Sroberto	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
169258945Sroberto	if (!data)
170258945Sroberto		return -ENOMEM;
171258945Sroberto
172258945Sroberto	data->reg_base = devm_platform_ioremap_resource(pdev, 0);
173258945Sroberto	if (IS_ERR(data->reg_base))
174258945Sroberto		return PTR_ERR(data->reg_base);
175258945Sroberto
176258945Sroberto	data->clk = devm_clk_get_enabled(dev, NULL);
177258945Sroberto	if (IS_ERR(data->clk))
178258945Sroberto		return PTR_ERR(data->clk);
179258945Sroberto
180258945Sroberto	params = (struct wdt_params *)of_device_get_match_data(dev);
181258945Sroberto
182258945Sroberto	platform_set_drvdata(pdev, data);
183258945Sroberto
184258945Sroberto	data->wdt_dev.parent = dev;
185258945Sroberto	data->wdt_dev.info = &meson_gxbb_wdt_info;
186258945Sroberto	data->wdt_dev.ops = &meson_gxbb_wdt_ops;
187258945Sroberto	data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK;
188258945Sroberto	data->wdt_dev.min_timeout = 1;
189258945Sroberto	data->wdt_dev.timeout = DEFAULT_TIMEOUT;
190258945Sroberto	watchdog_init_timeout(&data->wdt_dev, timeout, dev);
191258945Sroberto	watchdog_set_nowayout(&data->wdt_dev, nowayout);
192258945Sroberto	watchdog_set_drvdata(&data->wdt_dev, data);
193258945Sroberto
194258945Sroberto	ctrl_reg = readl(data->reg_base + GXBB_WDT_CTRL_REG) &
195258945Sroberto				GXBB_WDT_CTRL_EN;
196258945Sroberto
197258945Sroberto	if (ctrl_reg) {
198258945Sroberto		/* Watchdog is running - keep it running but extend timeout
199258945Sroberto		 * to the maximum while setting the timebase
200258945Sroberto		 */
201258945Sroberto		set_bit(WDOG_HW_RUNNING, &data->wdt_dev.status);
202258945Sroberto		meson_gxbb_wdt_set_timeout(&data->wdt_dev,
203258945Sroberto				GXBB_WDT_TCNT_SETUP_MASK / 1000);
204258945Sroberto	}
205258945Sroberto
206258945Sroberto	/* Setup with 1ms timebase */
207258945Sroberto	ctrl_reg |= ((clk_get_rate(data->clk) / 1000) &
208258945Sroberto			GXBB_WDT_CTRL_DIV_MASK) |
209258945Sroberto			params->rst |
210258945Sroberto			GXBB_WDT_CTRL_CLK_EN |
211258945Sroberto			GXBB_WDT_CTRL_CLKDIV_EN;
212258945Sroberto
213258945Sroberto	writel(ctrl_reg, data->reg_base + GXBB_WDT_CTRL_REG);
214258945Sroberto	meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
215258945Sroberto
216258945Sroberto	return devm_watchdog_register_device(dev, &data->wdt_dev);
217258945Sroberto}
218258945Sroberto
219258945Srobertostatic struct platform_driver meson_gxbb_wdt_driver = {
220258945Sroberto	.probe	= meson_gxbb_wdt_probe,
221258945Sroberto	.driver = {
222258945Sroberto		.name = "meson-gxbb-wdt",
223258945Sroberto		.pm = &meson_gxbb_wdt_pm_ops,
224258945Sroberto		.of_match_table	= meson_gxbb_wdt_dt_ids,
225258945Sroberto	},
226258945Sroberto};
227258945Sroberto
228258945Srobertomodule_platform_driver(meson_gxbb_wdt_driver);
229258945Sroberto
230258945SrobertoMODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
231258945SrobertoMODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver");
232258945SrobertoMODULE_LICENSE("Dual BSD/GPL");
233258945Sroberto