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