Blame view

drivers/watchdog/da9062_wdt.c 6.88 KB
2e62c4988   Marcus Folkesson   watchdog: add SPD...
1
  // SPDX-License-Identifier: GPL-2.0+
7a7cb009d   S Twiss   watchdog: da9062:...
2
  /*
72106c189   Steve Twiss   watchdog: da9062/...
3
   * Watchdog device driver for DA9062 and DA9061 PMICs
7a7cb009d   S Twiss   watchdog: da9062:...
4
5
   * Copyright (C) 2015  Dialog Semiconductor Ltd.
   *
7a7cb009d   S Twiss   watchdog: da9062:...
6
7
8
9
10
11
12
13
   */
  
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/watchdog.h>
  #include <linux/platform_device.h>
  #include <linux/uaccess.h>
  #include <linux/slab.h>
057b52b4b   Marco Felsch   watchdog: da9062:...
14
  #include <linux/i2c.h>
7a7cb009d   S Twiss   watchdog: da9062:...
15
16
17
18
  #include <linux/delay.h>
  #include <linux/jiffies.h>
  #include <linux/mfd/da9062/registers.h>
  #include <linux/mfd/da9062/core.h>
8541673d2   Marco Felsch   watchdog: da9062:...
19
  #include <linux/property.h>
7a7cb009d   S Twiss   watchdog: da9062:...
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  #include <linux/regmap.h>
  #include <linux/of.h>
  
  static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
  #define DA9062_TWDSCALE_DISABLE		0
  #define DA9062_TWDSCALE_MIN		1
  #define DA9062_TWDSCALE_MAX		(ARRAY_SIZE(wdt_timeout) - 1)
  #define DA9062_WDT_MIN_TIMEOUT		wdt_timeout[DA9062_TWDSCALE_MIN]
  #define DA9062_WDT_MAX_TIMEOUT		wdt_timeout[DA9062_TWDSCALE_MAX]
  #define DA9062_WDG_DEFAULT_TIMEOUT	wdt_timeout[DA9062_TWDSCALE_MAX-1]
  #define DA9062_RESET_PROTECTION_MS	300
  
  struct da9062_watchdog {
  	struct da9062 *hw;
  	struct watchdog_device wdtdev;
8541673d2   Marco Felsch   watchdog: da9062:...
35
  	bool use_sw_pm;
7a7cb009d   S Twiss   watchdog: da9062:...
36
  };
e8799ce85   Stefan Riedmueller   watchdog: da9062:...
37
38
39
40
41
42
43
44
  static unsigned int da9062_wdt_read_timeout(struct da9062_watchdog *wdt)
  {
  	unsigned int val;
  
  	regmap_read(wdt->hw->regmap, DA9062AA_CONTROL_D, &val);
  
  	return wdt_timeout[val & DA9062AA_TWDSCALE_MASK];
  }
7a7cb009d   S Twiss   watchdog: da9062:...
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs)
  {
  	unsigned int i;
  
  	for (i = DA9062_TWDSCALE_MIN; i <= DA9062_TWDSCALE_MAX; i++) {
  		if (wdt_timeout[i] >= secs)
  			return i;
  	}
  
  	return DA9062_TWDSCALE_MAX;
  }
  
  static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt)
  {
6e8a7c75d   Guenter Roeck   watchdog: da9062_...
59
60
61
  	return regmap_update_bits(wdt->hw->regmap, DA9062AA_CONTROL_F,
  				  DA9062AA_WATCHDOG_MASK,
  				  DA9062AA_WATCHDOG_MASK);
7a7cb009d   S Twiss   watchdog: da9062:...
62
63
64
65
66
67
  }
  
  static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt,
  					      unsigned int regval)
  {
  	struct da9062 *chip = wdt->hw;
7a7cb009d   S Twiss   watchdog: da9062:...
68

f31b2a9bd   Michael Grzeschik   watchdog: da9062:...
69
70
71
72
73
74
  	regmap_update_bits(chip->regmap,
  				  DA9062AA_CONTROL_D,
  				  DA9062AA_TWDSCALE_MASK,
  				  DA9062_TWDSCALE_DISABLE);
  
  	usleep_range(150, 300);
7a7cb009d   S Twiss   watchdog: da9062:...
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
  	return regmap_update_bits(chip->regmap,
  				  DA9062AA_CONTROL_D,
  				  DA9062AA_TWDSCALE_MASK,
  				  regval);
  }
  
  static int da9062_wdt_start(struct watchdog_device *wdd)
  {
  	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  	unsigned int selector;
  	int ret;
  
  	selector = da9062_wdt_timeout_to_sel(wdt->wdtdev.timeout);
  	ret = da9062_wdt_update_timeout_register(wdt, selector);
  	if (ret)
  		dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)
  ",
  			ret);
  
  	return ret;
  }
  
  static int da9062_wdt_stop(struct watchdog_device *wdd)
  {
  	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  	int ret;
7a7cb009d   S Twiss   watchdog: da9062:...
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
  	ret = regmap_update_bits(wdt->hw->regmap,
  				 DA9062AA_CONTROL_D,
  				 DA9062AA_TWDSCALE_MASK,
  				 DA9062_TWDSCALE_DISABLE);
  	if (ret)
  		dev_err(wdt->hw->dev, "Watchdog failed to stop (err = %d)
  ",
  			ret);
  
  	return ret;
  }
  
  static int da9062_wdt_ping(struct watchdog_device *wdd)
  {
  	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  	int ret;
  
  	ret = da9062_reset_watchdog_timer(wdt);
  	if (ret)
  		dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)
  ",
  			ret);
  
  	return ret;
  }
  
  static int da9062_wdt_set_timeout(struct watchdog_device *wdd,
  				  unsigned int timeout)
  {
  	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  	unsigned int selector;
  	int ret;
  
  	selector = da9062_wdt_timeout_to_sel(timeout);
  	ret = da9062_wdt_update_timeout_register(wdt, selector);
  	if (ret)
  		dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)
  ",
  			ret);
  	else
  		wdd->timeout = wdt_timeout[selector];
  
  	return ret;
  }
540f63519   Michael Grzeschik   watchdog: da9062:...
145
146
147
148
  static int da9062_wdt_restart(struct watchdog_device *wdd, unsigned long action,
  			      void *data)
  {
  	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
057b52b4b   Marco Felsch   watchdog: da9062:...
149
  	struct i2c_client *client = to_i2c_client(wdt->hw->dev);
540f63519   Michael Grzeschik   watchdog: da9062:...
150
  	int ret;
057b52b4b   Marco Felsch   watchdog: da9062:...
151
152
153
154
  	/* Don't use regmap because it is not atomic safe */
  	ret = i2c_smbus_write_byte_data(client, DA9062AA_CONTROL_F,
  					DA9062AA_SHUTDOWN_MASK);
  	if (ret < 0)
540f63519   Michael Grzeschik   watchdog: da9062:...
155
156
157
158
159
160
161
162
163
  		dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)
  ",
  			  ret);
  
  	/* wait for reset to assert... */
  	mdelay(500);
  
  	return ret;
  }
7a7cb009d   S Twiss   watchdog: da9062:...
164
165
166
167
168
169
170
171
172
173
174
  static const struct watchdog_info da9062_watchdog_info = {
  	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
  	.identity = "DA9062 WDT",
  };
  
  static const struct watchdog_ops da9062_watchdog_ops = {
  	.owner = THIS_MODULE,
  	.start = da9062_wdt_start,
  	.stop = da9062_wdt_stop,
  	.ping = da9062_wdt_ping,
  	.set_timeout = da9062_wdt_set_timeout,
540f63519   Michael Grzeschik   watchdog: da9062:...
175
  	.restart = da9062_wdt_restart,
7a7cb009d   S Twiss   watchdog: da9062:...
176
  };
72106c189   Steve Twiss   watchdog: da9062/...
177
178
179
180
181
182
  static const struct of_device_id da9062_compatible_id_table[] = {
  	{ .compatible = "dlg,da9062-watchdog", },
  	{ },
  };
  
  MODULE_DEVICE_TABLE(of, da9062_compatible_id_table);
7a7cb009d   S Twiss   watchdog: da9062:...
183
184
  static int da9062_wdt_probe(struct platform_device *pdev)
  {
6e8a7c75d   Guenter Roeck   watchdog: da9062_...
185
  	struct device *dev = &pdev->dev;
e8799ce85   Stefan Riedmueller   watchdog: da9062:...
186
  	unsigned int timeout;
7a7cb009d   S Twiss   watchdog: da9062:...
187
188
  	struct da9062 *chip;
  	struct da9062_watchdog *wdt;
6e8a7c75d   Guenter Roeck   watchdog: da9062_...
189
  	chip = dev_get_drvdata(dev->parent);
7a7cb009d   S Twiss   watchdog: da9062:...
190
191
  	if (!chip)
  		return -EINVAL;
6e8a7c75d   Guenter Roeck   watchdog: da9062_...
192
  	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
7a7cb009d   S Twiss   watchdog: da9062:...
193
194
  	if (!wdt)
  		return -ENOMEM;
8541673d2   Marco Felsch   watchdog: da9062:...
195
  	wdt->use_sw_pm = device_property_present(dev, "dlg,use-sw-pm");
7a7cb009d   S Twiss   watchdog: da9062:...
196
197
198
199
200
201
  	wdt->hw = chip;
  
  	wdt->wdtdev.info = &da9062_watchdog_info;
  	wdt->wdtdev.ops = &da9062_watchdog_ops;
  	wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT;
  	wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT;
fb484262f   Michael Grzeschik   watchdog: da9062:...
202
  	wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS;
7a7cb009d   S Twiss   watchdog: da9062:...
203
204
  	wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT;
  	wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
6e8a7c75d   Guenter Roeck   watchdog: da9062_...
205
  	wdt->wdtdev.parent = dev;
7a7cb009d   S Twiss   watchdog: da9062:...
206

540f63519   Michael Grzeschik   watchdog: da9062:...
207
  	watchdog_set_restart_priority(&wdt->wdtdev, 128);
7a7cb009d   S Twiss   watchdog: da9062:...
208
  	watchdog_set_drvdata(&wdt->wdtdev, wdt);
f6c98b083   Marco Felsch   watchdog: da9062:...
209
  	dev_set_drvdata(dev, &wdt->wdtdev);
7a7cb009d   S Twiss   watchdog: da9062:...
210

e8799ce85   Stefan Riedmueller   watchdog: da9062:...
211
212
213
214
215
216
217
218
219
220
221
  	timeout = da9062_wdt_read_timeout(wdt);
  	if (timeout)
  		wdt->wdtdev.timeout = timeout;
  
  	/* Set timeout from DT value if available */
  	watchdog_init_timeout(&wdt->wdtdev, 0, dev);
  
  	if (timeout) {
  		da9062_wdt_set_timeout(&wdt->wdtdev, wdt->wdtdev.timeout);
  		set_bit(WDOG_HW_RUNNING, &wdt->wdtdev.status);
  	}
7a7cb009d   S Twiss   watchdog: da9062:...
222

e8799ce85   Stefan Riedmueller   watchdog: da9062:...
223
  	return devm_watchdog_register_device(dev, &wdt->wdtdev);
7a7cb009d   S Twiss   watchdog: da9062:...
224
  }
f6c98b083   Marco Felsch   watchdog: da9062:...
225
226
227
  static int __maybe_unused da9062_wdt_suspend(struct device *dev)
  {
  	struct watchdog_device *wdd = dev_get_drvdata(dev);
8541673d2   Marco Felsch   watchdog: da9062:...
228
229
230
231
  	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  
  	if (!wdt->use_sw_pm)
  		return 0;
f6c98b083   Marco Felsch   watchdog: da9062:...
232
233
234
235
236
237
238
239
240
241
  
  	if (watchdog_active(wdd))
  		return da9062_wdt_stop(wdd);
  
  	return 0;
  }
  
  static int __maybe_unused da9062_wdt_resume(struct device *dev)
  {
  	struct watchdog_device *wdd = dev_get_drvdata(dev);
8541673d2   Marco Felsch   watchdog: da9062:...
242
243
244
245
  	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  
  	if (!wdt->use_sw_pm)
  		return 0;
f6c98b083   Marco Felsch   watchdog: da9062:...
246
247
248
249
250
251
252
253
254
  
  	if (watchdog_active(wdd))
  		return da9062_wdt_start(wdd);
  
  	return 0;
  }
  
  static SIMPLE_DEV_PM_OPS(da9062_wdt_pm_ops,
  			 da9062_wdt_suspend, da9062_wdt_resume);
7a7cb009d   S Twiss   watchdog: da9062:...
255
256
  static struct platform_driver da9062_wdt_driver = {
  	.probe = da9062_wdt_probe,
7a7cb009d   S Twiss   watchdog: da9062:...
257
258
  	.driver = {
  		.name = "da9062-watchdog",
f6c98b083   Marco Felsch   watchdog: da9062:...
259
  		.pm = &da9062_wdt_pm_ops,
72106c189   Steve Twiss   watchdog: da9062/...
260
  		.of_match_table = da9062_compatible_id_table,
7a7cb009d   S Twiss   watchdog: da9062:...
261
262
263
264
265
  	},
  };
  module_platform_driver(da9062_wdt_driver);
  
  MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
72106c189   Steve Twiss   watchdog: da9062/...
266
  MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061");
7a7cb009d   S Twiss   watchdog: da9062:...
267
268
  MODULE_LICENSE("GPL");
  MODULE_ALIAS("platform:da9062-watchdog");