summaryrefslogtreecommitdiff
path: root/drivers/watchdog/rti_wdt.c
blob: d456dd72d99a0854a361d4ce6abb96db724d55fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// SPDX-License-Identifier: GPL-2.0
/*
 * Watchdog driver for the K3 RTI module
 *
 * (c) Copyright 2019-2020 Texas Instruments Inc.
 * All rights reserved.
 */

#include <linux/clk.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
#include <linux/watchdog.h>

#define DEFAULT_HEARTBEAT 60

/* Max heartbeat is calculated at 32kHz source clock */
#define MAX_HEARTBEAT	1000

/* Timer register set definition */
#define RTIDWDCTRL	0x90
#define RTIDWDPRLD	0x94
#define RTIWDSTATUS	0x98
#define RTIWDKEY	0x9c
#define RTIDWDCNTR	0xa0
#define RTIWWDRXCTRL	0xa4
#define RTIWWDSIZECTRL	0xa8

#define RTIWWDRX_NMI	0xa

#define RTIWWDSIZE_50P	0x50

#define WDENABLE_KEY	0xa98559da

#define WDKEY_SEQ0		0xe51a
#define WDKEY_SEQ1		0xa35c

#define WDT_PRELOAD_SHIFT	13

#define WDT_PRELOAD_MAX		0xfff

#define DWDST			BIT(1)

static int heartbeat;

/*
 * struct to hold data for each WDT device
 * @base - base io address of WD device
 * @freq - source clock frequency of WDT
 * @wdd  - hold watchdog device as is in WDT core
 */
struct rti_wdt_device {
	void __iomem		*base;
	unsigned long		freq;
	struct watchdog_device	wdd;
};

static int rti_wdt_start(struct watchdog_device *wdd)
{
	u32 timer_margin;
	struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);

	/* set timeout period */
	timer_margin = (u64)wdd->timeout * wdt->freq;
	timer_margin >>= WDT_PRELOAD_SHIFT;
	if (timer_margin > WDT_PRELOAD_MAX)
		timer_margin = WDT_PRELOAD_MAX;
	writel_relaxed(timer_margin, wdt->base + RTIDWDPRLD);

	/*
	 * RTI only supports a windowed mode, where the watchdog can only
	 * be petted during the open window; not too early or not too late.
	 * The HW configuration options only allow for the open window size
	 * to be 50% or less than that; we obviouly want to configure the open
	 * window as large as possible so we select the 50% option. To avoid
	 * any glitches, we accommodate 5% safety margin also, so we setup
	 * the min_hw_hearbeat at 55% of the timeout period.
	 */
	wdd->min_hw_heartbeat_ms = 11 * wdd->timeout * 1000 / 20;

	/* Generate NMI when wdt expires */
	writel_relaxed(RTIWWDRX_NMI, wdt->base + RTIWWDRXCTRL);

	/* Open window size 50%; this is the largest window size available */
	writel_relaxed(RTIWWDSIZE_50P, wdt->base + RTIWWDSIZECTRL);

	readl_relaxed(wdt->base + RTIWWDSIZECTRL);

	/* enable watchdog */
	writel_relaxed(WDENABLE_KEY, wdt->base + RTIDWDCTRL);
	return 0;
}

static int rti_wdt_ping(struct watchdog_device *wdd)
{
	struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);

	/* put watchdog in service state */
	writel_relaxed(WDKEY_SEQ0, wdt->base + RTIWDKEY);
	/* put watchdog in active state */
	writel_relaxed(WDKEY_SEQ1, wdt->base + RTIWDKEY);

	return 0;
}

static unsigned int rti_wdt_get_timeleft(struct watchdog_device *wdd)
{
	u64 timer_counter;
	u32 val;
	struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);

	/* if timeout has occurred then return 0 */
	val = readl_relaxed(wdt->base + RTIWDSTATUS);
	if (val & DWDST)
		return 0;

	timer_counter = readl_relaxed(wdt->base + RTIDWDCNTR);

	do_div(timer_counter, wdt->freq);

	return timer_counter;
}

static const struct watchdog_info rti_wdt_info = {
	.options = WDIOF_KEEPALIVEPING,
	.identity = "K3 RTI Watchdog",
};

static const struct watchdog_ops rti_wdt_ops = {
	.owner		= THIS_MODULE,
	.start		= rti_wdt_start,
	.ping		= rti_wdt_ping,
	.get_timeleft	= rti_wdt_get_timeleft,
};

static int rti_wdt_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct device *dev = &pdev->dev;
	struct resource *wdt_mem;
	struct watchdog_device *wdd;
	struct rti_wdt_device *wdt;
	struct clk *clk;

	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
	if (!wdt)
		return -ENOMEM;

	clk = clk_get(dev, NULL);
	if (IS_ERR(clk)) {
		if (PTR_ERR(clk) != -EPROBE_DEFER)
			dev_err(dev, "failed to get clock\n");
		return PTR_ERR(clk);
	}

	wdt->freq = clk_get_rate(clk);

	clk_put(clk);

	if (!wdt->freq) {
		dev_err(dev, "Failed to get fck rate.\n");
		return -EINVAL;
	}

	pm_runtime_enable(dev);
	ret = pm_runtime_get_sync(dev);
	if (ret) {
		if (ret != -EPROBE_DEFER)
			dev_err(&pdev->dev, "runtime pm failed\n");
		return ret;
	}

	platform_set_drvdata(pdev, wdt);

	wdd = &wdt->wdd;
	wdd->info = &rti_wdt_info;
	wdd->ops = &rti_wdt_ops;
	wdd->min_timeout = 1;
	wdd->max_hw_heartbeat_ms = (WDT_PRELOAD_MAX << WDT_PRELOAD_SHIFT) /
		wdt->freq * 1000;
	wdd->timeout = DEFAULT_HEARTBEAT;
	wdd->parent = dev;

	watchdog_init_timeout(wdd, heartbeat, dev);

	watchdog_set_drvdata(wdd, wdt);
	watchdog_set_nowayout(wdd, 1);
	watchdog_set_restart_priority(wdd, 128);

	wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	wdt->base = devm_ioremap_resource(dev, wdt_mem);
	if (IS_ERR(wdt->base)) {
		ret = PTR_ERR(wdt->base);
		goto err_iomap;
	}

	ret = watchdog_register_device(wdd);
	if (ret) {
		dev_err(dev, "cannot register watchdog device\n");
		goto err_iomap;
	}

	return 0;

err_iomap:
	pm_runtime_put_sync(&pdev->dev);

	return ret;
}

static int rti_wdt_remove(struct platform_device *pdev)
{
	struct rti_wdt_device *wdt = platform_get_drvdata(pdev);

	watchdog_unregister_device(&wdt->wdd);
	pm_runtime_put(&pdev->dev);

	return 0;
}

static const struct of_device_id rti_wdt_of_match[] = {
	{ .compatible = "ti,j7-rti-wdt", },
	{},
};
MODULE_DEVICE_TABLE(of, rti_wdt_of_match);

static struct platform_driver rti_wdt_driver = {
	.driver = {
		.name = "rti-wdt",
		.of_match_table = rti_wdt_of_match,
	},
	.probe = rti_wdt_probe,
	.remove = rti_wdt_remove,
};

module_platform_driver(rti_wdt_driver);

MODULE_AUTHOR("Tero Kristo <t-kristo@ti.com>");
MODULE_DESCRIPTION("K3 RTI Watchdog Driver");

module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat,
		 "Watchdog heartbeat period in seconds from 1 to "
		 __MODULE_STRING(MAX_HEARTBEAT) ", default "
		 __MODULE_STRING(DEFAULT_HEARTBEAT));

MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:rti-wdt");