Blame view

drivers/watchdog/qcom-wdt.c 6.37 KB
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
1
2
3
4
5
6
7
8
9
10
11
12
13
  /* Copyright (c) 2014, The Linux Foundation. All rights reserved.
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 and
   * only version 2 as published by the Free Software Foundation.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   */
  #include <linux/clk.h>
05e487d90   Josh Cartwright   watchdog: qcom: r...
14
  #include <linux/delay.h>
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
15
16
17
18
19
20
  #include <linux/io.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/of.h>
  #include <linux/platform_device.h>
  #include <linux/watchdog.h>
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
21
  #include <linux/of_device.h>
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
22

f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
23
24
25
26
  enum wdt_reg {
  	WDT_RST,
  	WDT_EN,
  	WDT_STS,
10073a205   Matthew McClintock   watchdog: qcom: c...
27
  	WDT_BARK_TIME,
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
28
29
30
31
32
33
34
  	WDT_BITE_TIME,
  };
  
  static const u32 reg_offset_data_apcs_tmr[] = {
  	[WDT_RST] = 0x38,
  	[WDT_EN] = 0x40,
  	[WDT_STS] = 0x44,
10073a205   Matthew McClintock   watchdog: qcom: c...
35
  	[WDT_BARK_TIME] = 0x4C,
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
36
37
38
39
40
41
42
  	[WDT_BITE_TIME] = 0x5C,
  };
  
  static const u32 reg_offset_data_kpss[] = {
  	[WDT_RST] = 0x4,
  	[WDT_EN] = 0x8,
  	[WDT_STS] = 0xC,
10073a205   Matthew McClintock   watchdog: qcom: c...
43
  	[WDT_BARK_TIME] = 0x10,
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
44
45
  	[WDT_BITE_TIME] = 0x14,
  };
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
46
47
48
49
50
51
  
  struct qcom_wdt {
  	struct watchdog_device	wdd;
  	struct clk		*clk;
  	unsigned long		rate;
  	void __iomem		*base;
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
52
  	const u32		*layout;
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
53
  };
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
54
55
56
57
  static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg)
  {
  	return wdt->base + wdt->layout[reg];
  }
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
58
59
60
61
62
63
64
65
66
  static inline
  struct qcom_wdt *to_qcom_wdt(struct watchdog_device *wdd)
  {
  	return container_of(wdd, struct qcom_wdt, wdd);
  }
  
  static int qcom_wdt_start(struct watchdog_device *wdd)
  {
  	struct qcom_wdt *wdt = to_qcom_wdt(wdd);
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
67
68
  	writel(0, wdt_addr(wdt, WDT_EN));
  	writel(1, wdt_addr(wdt, WDT_RST));
10073a205   Matthew McClintock   watchdog: qcom: c...
69
  	writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME));
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
70
71
  	writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME));
  	writel(1, wdt_addr(wdt, WDT_EN));
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
72
73
74
75
76
77
  	return 0;
  }
  
  static int qcom_wdt_stop(struct watchdog_device *wdd)
  {
  	struct qcom_wdt *wdt = to_qcom_wdt(wdd);
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
78
  	writel(0, wdt_addr(wdt, WDT_EN));
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
79
80
81
82
83
84
  	return 0;
  }
  
  static int qcom_wdt_ping(struct watchdog_device *wdd)
  {
  	struct qcom_wdt *wdt = to_qcom_wdt(wdd);
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
85
  	writel(1, wdt_addr(wdt, WDT_RST));
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
86
87
88
89
90
91
92
93
94
  	return 0;
  }
  
  static int qcom_wdt_set_timeout(struct watchdog_device *wdd,
  				unsigned int timeout)
  {
  	wdd->timeout = timeout;
  	return qcom_wdt_start(wdd);
  }
4d8b229d5   Guenter Roeck   watchdog: Add 'ac...
95
96
  static int qcom_wdt_restart(struct watchdog_device *wdd, unsigned long action,
  			    void *data)
05e487d90   Josh Cartwright   watchdog: qcom: r...
97
  {
80969a68f   Damien Riegel   watchdog: qcom-wd...
98
  	struct qcom_wdt *wdt = to_qcom_wdt(wdd);
05e487d90   Josh Cartwright   watchdog: qcom: r...
99
100
101
102
103
104
105
  	u32 timeout;
  
  	/*
  	 * Trigger watchdog bite:
  	 *    Setup BITE_TIME to be 128ms, and enable WDT.
  	 */
  	timeout = 128 * wdt->rate / 1000;
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
106
107
  	writel(0, wdt_addr(wdt, WDT_EN));
  	writel(1, wdt_addr(wdt, WDT_RST));
10073a205   Matthew McClintock   watchdog: qcom: c...
108
  	writel(timeout, wdt_addr(wdt, WDT_BARK_TIME));
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
109
110
  	writel(timeout, wdt_addr(wdt, WDT_BITE_TIME));
  	writel(1, wdt_addr(wdt, WDT_EN));
05e487d90   Josh Cartwright   watchdog: qcom: r...
111
112
113
114
115
116
117
  
  	/*
  	 * Actually make sure the above sequence hits hardware before sleeping.
  	 */
  	wmb();
  
  	msleep(150);
80969a68f   Damien Riegel   watchdog: qcom-wd...
118
  	return 0;
05e487d90   Josh Cartwright   watchdog: qcom: r...
119
  }
80969a68f   Damien Riegel   watchdog: qcom-wd...
120
121
122
123
124
125
126
127
128
129
130
131
  static const struct watchdog_ops qcom_wdt_ops = {
  	.start		= qcom_wdt_start,
  	.stop		= qcom_wdt_stop,
  	.ping		= qcom_wdt_ping,
  	.set_timeout	= qcom_wdt_set_timeout,
  	.restart        = qcom_wdt_restart,
  	.owner		= THIS_MODULE,
  };
  
  static const struct watchdog_info qcom_wdt_info = {
  	.options	= WDIOF_KEEPALIVEPING
  			| WDIOF_MAGICCLOSE
b6ef36d2c   Guenter Roeck   watchdog: qcom: R...
132
133
  			| WDIOF_SETTIMEOUT
  			| WDIOF_CARDRESET,
80969a68f   Damien Riegel   watchdog: qcom-wd...
134
135
  	.identity	= KBUILD_MODNAME,
  };
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
136
137
138
139
  static int qcom_wdt_probe(struct platform_device *pdev)
  {
  	struct qcom_wdt *wdt;
  	struct resource *res;
0dfd582e0   Mathieu Olivari   watchdog: qcom: u...
140
  	struct device_node *np = pdev->dev.of_node;
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
141
  	const u32 *regs;
0dfd582e0   Mathieu Olivari   watchdog: qcom: u...
142
  	u32 percpu_offset;
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
143
  	int ret;
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
144
145
146
147
148
149
  	regs = of_device_get_match_data(&pdev->dev);
  	if (!regs) {
  		dev_err(&pdev->dev, "Unsupported QCOM WDT module
  ");
  		return -ENODEV;
  	}
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
150
151
152
153
154
  	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
  	if (!wdt)
  		return -ENOMEM;
  
  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
0dfd582e0   Mathieu Olivari   watchdog: qcom: u...
155
156
157
158
159
160
161
  
  	/* We use CPU0's DGT for the watchdog */
  	if (of_property_read_u32(np, "cpu-offset", &percpu_offset))
  		percpu_offset = 0;
  
  	res->start += percpu_offset;
  	res->end += percpu_offset;
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
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
  	wdt->base = devm_ioremap_resource(&pdev->dev, res);
  	if (IS_ERR(wdt->base))
  		return PTR_ERR(wdt->base);
  
  	wdt->clk = devm_clk_get(&pdev->dev, NULL);
  	if (IS_ERR(wdt->clk)) {
  		dev_err(&pdev->dev, "failed to get input clock
  ");
  		return PTR_ERR(wdt->clk);
  	}
  
  	ret = clk_prepare_enable(wdt->clk);
  	if (ret) {
  		dev_err(&pdev->dev, "failed to setup clock
  ");
  		return ret;
  	}
  
  	/*
  	 * We use the clock rate to calculate the max timeout, so ensure it's
  	 * not zero to avoid a divide-by-zero exception.
  	 *
  	 * WATCHDOG_CORE assumes units of seconds, if the WDT is clocked such
  	 * that it would bite before a second elapses it's usefulness is
  	 * limited.  Bail if this is the case.
  	 */
  	wdt->rate = clk_get_rate(wdt->clk);
  	if (wdt->rate == 0 ||
  	    wdt->rate > 0x10000000U) {
  		dev_err(&pdev->dev, "invalid clock rate
  ");
  		ret = -EINVAL;
  		goto err_clk_unprepare;
  	}
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
196
197
198
199
  	wdt->wdd.info = &qcom_wdt_info;
  	wdt->wdd.ops = &qcom_wdt_ops;
  	wdt->wdd.min_timeout = 1;
  	wdt->wdd.max_timeout = 0x10000000U / wdt->rate;
6551881c8   Pratyush Anand   Watchdog: Fix par...
200
  	wdt->wdd.parent = &pdev->dev;
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
201
  	wdt->layout = regs;
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
202

9b78d6905   Christian Lamparter   watchdog: qcom: f...
203
  	if (readl(wdt_addr(wdt, WDT_STS)) & 1)
b6ef36d2c   Guenter Roeck   watchdog: qcom: R...
204
  		wdt->wdd.bootstatus = WDIOF_CARDRESET;
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
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
  	/*
  	 * If 'timeout-sec' unspecified in devicetree, assume a 30 second
  	 * default, unless the max timeout is less than 30 seconds, then use
  	 * the max instead.
  	 */
  	wdt->wdd.timeout = min(wdt->wdd.max_timeout, 30U);
  	watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev);
  
  	ret = watchdog_register_device(&wdt->wdd);
  	if (ret) {
  		dev_err(&pdev->dev, "failed to register watchdog
  ");
  		goto err_clk_unprepare;
  	}
  
  	platform_set_drvdata(pdev, wdt);
  	return 0;
  
  err_clk_unprepare:
  	clk_disable_unprepare(wdt->clk);
  	return ret;
  }
  
  static int qcom_wdt_remove(struct platform_device *pdev)
  {
  	struct qcom_wdt *wdt = platform_get_drvdata(pdev);
  
  	watchdog_unregister_device(&wdt->wdd);
  	clk_disable_unprepare(wdt->clk);
  	return 0;
  }
  
  static const struct of_device_id qcom_wdt_of_table[] = {
f0d9d0f4b   Matthew McClintock   watchdog: qcom: a...
238
239
240
  	{ .compatible = "qcom,kpss-timer", .data = reg_offset_data_apcs_tmr },
  	{ .compatible = "qcom,scss-timer", .data = reg_offset_data_apcs_tmr },
  	{ .compatible = "qcom,kpss-wdt", .data = reg_offset_data_kpss },
1094ebe9d   Josh Cartwright   watchdog: qcom: a...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
  	{ },
  };
  MODULE_DEVICE_TABLE(of, qcom_wdt_of_table);
  
  static struct platform_driver qcom_watchdog_driver = {
  	.probe	= qcom_wdt_probe,
  	.remove	= qcom_wdt_remove,
  	.driver	= {
  		.name		= KBUILD_MODNAME,
  		.of_match_table	= qcom_wdt_of_table,
  	},
  };
  module_platform_driver(qcom_watchdog_driver);
  
  MODULE_DESCRIPTION("QCOM KPSS Watchdog Driver");
  MODULE_LICENSE("GPL v2");