xref: /openbmc/linux/drivers/watchdog/da9063_wdt.c (revision bc5aa3a0)
1 /*
2  * Watchdog driver for DA9063 PMICs.
3  *
4  * Copyright(c) 2012 Dialog Semiconductor Ltd.
5  *
6  * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>
7  *
8  * This program is free software; you can redistribute  it and/or modify it
9  * under  the terms of  the GNU General  Public License as published by the
10  * Free Software Foundation;  either version 2 of the  License, or (at your
11  * option) any later version.
12  */
13 
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/watchdog.h>
17 #include <linux/platform_device.h>
18 #include <linux/uaccess.h>
19 #include <linux/slab.h>
20 #include <linux/delay.h>
21 #include <linux/mfd/da9063/registers.h>
22 #include <linux/mfd/da9063/core.h>
23 #include <linux/regmap.h>
24 
25 /*
26  * Watchdog selector to timeout in seconds.
27  *   0: WDT disabled;
28  *   others: timeout = 2048 ms * 2^(TWDSCALE-1).
29  */
30 static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
31 #define DA9063_TWDSCALE_DISABLE		0
32 #define DA9063_TWDSCALE_MIN		1
33 #define DA9063_TWDSCALE_MAX		(ARRAY_SIZE(wdt_timeout) - 1)
34 #define DA9063_WDT_MIN_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MIN]
35 #define DA9063_WDT_MAX_TIMEOUT		wdt_timeout[DA9063_TWDSCALE_MAX]
36 #define DA9063_WDG_TIMEOUT		wdt_timeout[3]
37 #define DA9063_RESET_PROTECTION_MS	256
38 
39 struct da9063_watchdog {
40 	struct da9063 *da9063;
41 	struct watchdog_device wdtdev;
42 };
43 
44 static unsigned int da9063_wdt_timeout_to_sel(unsigned int secs)
45 {
46 	unsigned int i;
47 
48 	for (i = DA9063_TWDSCALE_MIN; i <= DA9063_TWDSCALE_MAX; i++) {
49 		if (wdt_timeout[i] >= secs)
50 			return i;
51 	}
52 
53 	return DA9063_TWDSCALE_MAX;
54 }
55 
56 static int _da9063_wdt_set_timeout(struct da9063 *da9063, unsigned int regval)
57 {
58 	return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
59 				  DA9063_TWDSCALE_MASK, regval);
60 }
61 
62 static int da9063_wdt_start(struct watchdog_device *wdd)
63 {
64 	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
65 	unsigned int selector;
66 	int ret;
67 
68 	selector = da9063_wdt_timeout_to_sel(wdt->wdtdev.timeout);
69 	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
70 	if (ret)
71 		dev_err(wdt->da9063->dev, "Watchdog failed to start (err = %d)\n",
72 			ret);
73 
74 	return ret;
75 }
76 
77 static int da9063_wdt_stop(struct watchdog_device *wdd)
78 {
79 	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
80 	int ret;
81 
82 	ret = regmap_update_bits(wdt->da9063->regmap, DA9063_REG_CONTROL_D,
83 				 DA9063_TWDSCALE_MASK, DA9063_TWDSCALE_DISABLE);
84 	if (ret)
85 		dev_alert(wdt->da9063->dev, "Watchdog failed to stop (err = %d)\n",
86 			  ret);
87 
88 	return ret;
89 }
90 
91 static int da9063_wdt_ping(struct watchdog_device *wdd)
92 {
93 	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
94 	int ret;
95 
96 	ret = regmap_write(wdt->da9063->regmap, DA9063_REG_CONTROL_F,
97 			   DA9063_WATCHDOG);
98 	if (ret)
99 		dev_alert(wdt->da9063->dev, "Failed to ping the watchdog (err = %d)\n",
100 			  ret);
101 
102 	return ret;
103 }
104 
105 static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
106 				  unsigned int timeout)
107 {
108 	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
109 	unsigned int selector;
110 	int ret;
111 
112 	selector = da9063_wdt_timeout_to_sel(timeout);
113 	ret = _da9063_wdt_set_timeout(wdt->da9063, selector);
114 	if (ret)
115 		dev_err(wdt->da9063->dev, "Failed to set watchdog timeout (err = %d)\n",
116 			ret);
117 	else
118 		wdd->timeout = wdt_timeout[selector];
119 
120 	return ret;
121 }
122 
123 static int da9063_wdt_restart(struct watchdog_device *wdd, unsigned long action,
124 			      void *data)
125 {
126 	struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
127 	int ret;
128 
129 	ret = regmap_write(wdt->da9063->regmap, DA9063_REG_CONTROL_F,
130 			   DA9063_SHUTDOWN);
131 	if (ret)
132 		dev_alert(wdt->da9063->dev, "Failed to shutdown (err = %d)\n",
133 			  ret);
134 
135 	return ret;
136 }
137 
138 static const struct watchdog_info da9063_watchdog_info = {
139 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
140 	.identity = "DA9063 Watchdog",
141 };
142 
143 static const struct watchdog_ops da9063_watchdog_ops = {
144 	.owner = THIS_MODULE,
145 	.start = da9063_wdt_start,
146 	.stop = da9063_wdt_stop,
147 	.ping = da9063_wdt_ping,
148 	.set_timeout = da9063_wdt_set_timeout,
149 	.restart = da9063_wdt_restart,
150 };
151 
152 static int da9063_wdt_probe(struct platform_device *pdev)
153 {
154 	int ret;
155 	struct da9063 *da9063;
156 	struct da9063_watchdog *wdt;
157 
158 	if (!pdev->dev.parent)
159 		return -EINVAL;
160 
161 	da9063 = dev_get_drvdata(pdev->dev.parent);
162 	if (!da9063)
163 		return -EINVAL;
164 
165 	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
166 	if (!wdt)
167 		return -ENOMEM;
168 
169 	wdt->da9063 = da9063;
170 
171 	wdt->wdtdev.info = &da9063_watchdog_info;
172 	wdt->wdtdev.ops = &da9063_watchdog_ops;
173 	wdt->wdtdev.min_timeout = DA9063_WDT_MIN_TIMEOUT;
174 	wdt->wdtdev.max_timeout = DA9063_WDT_MAX_TIMEOUT;
175 	wdt->wdtdev.min_hw_heartbeat_ms = DA9063_RESET_PROTECTION_MS;
176 	wdt->wdtdev.timeout = DA9063_WDG_TIMEOUT;
177 	wdt->wdtdev.parent = &pdev->dev;
178 
179 	wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
180 
181 	watchdog_set_restart_priority(&wdt->wdtdev, 128);
182 
183 	watchdog_set_drvdata(&wdt->wdtdev, wdt);
184 	dev_set_drvdata(&pdev->dev, wdt);
185 
186 	ret = watchdog_register_device(&wdt->wdtdev);
187 	if (ret)
188 		return ret;
189 
190 	return 0;
191 }
192 
193 static int da9063_wdt_remove(struct platform_device *pdev)
194 {
195 	struct da9063_watchdog *wdt = dev_get_drvdata(&pdev->dev);
196 
197 	watchdog_unregister_device(&wdt->wdtdev);
198 
199 	return 0;
200 }
201 
202 static struct platform_driver da9063_wdt_driver = {
203 	.probe = da9063_wdt_probe,
204 	.remove = da9063_wdt_remove,
205 	.driver = {
206 		.name = DA9063_DRVNAME_WATCHDOG,
207 	},
208 };
209 module_platform_driver(da9063_wdt_driver);
210 
211 MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>");
212 MODULE_DESCRIPTION("Watchdog driver for Dialog DA9063");
213 MODULE_LICENSE("GPL");
214 MODULE_ALIAS("platform:" DA9063_DRVNAME_WATCHDOG);
215