diff options
Diffstat (limited to 'drivers/watchdog/rn5t618_wdt.c')
-rw-r--r-- | drivers/watchdog/rn5t618_wdt.c | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/drivers/watchdog/rn5t618_wdt.c b/drivers/watchdog/rn5t618_wdt.c new file mode 100644 index 000000000000..d1c12278cb6a --- /dev/null +++ b/drivers/watchdog/rn5t618_wdt.c | |||
@@ -0,0 +1,198 @@ | |||
1 | /* | ||
2 | * Watchdog driver for Ricoh RN5T618 PMIC | ||
3 | * | ||
4 | * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * version 2 as published by the Free Software Foundation. | ||
9 | * | ||
10 | * You should have received a copy of the GNU General Public License | ||
11 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
12 | */ | ||
13 | |||
14 | #include <linux/device.h> | ||
15 | #include <linux/mfd/rn5t618.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/watchdog.h> | ||
19 | |||
20 | #define DRIVER_NAME "rn5t618-wdt" | ||
21 | |||
22 | static bool nowayout = WATCHDOG_NOWAYOUT; | ||
23 | static unsigned int timeout; | ||
24 | |||
25 | module_param(timeout, uint, 0); | ||
26 | MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds"); | ||
27 | |||
28 | module_param(nowayout, bool, 0); | ||
29 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | ||
30 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
31 | |||
32 | struct rn5t618_wdt { | ||
33 | struct watchdog_device wdt_dev; | ||
34 | struct rn5t618 *rn5t618; | ||
35 | }; | ||
36 | |||
37 | /* | ||
38 | * This array encodes the values of WDOGTIM field for the supported | ||
39 | * watchdog expiration times. If the watchdog is not accessed before | ||
40 | * the timer expiration, the PMU generates an interrupt and if the CPU | ||
41 | * doesn't clear it within one second the system is restarted. | ||
42 | */ | ||
43 | static const struct { | ||
44 | u8 reg_val; | ||
45 | unsigned int time; | ||
46 | } rn5t618_wdt_map[] = { | ||
47 | { 0, 1 }, | ||
48 | { 1, 8 }, | ||
49 | { 2, 32 }, | ||
50 | { 3, 128 }, | ||
51 | }; | ||
52 | |||
53 | static int rn5t618_wdt_set_timeout(struct watchdog_device *wdt_dev, | ||
54 | unsigned int t) | ||
55 | { | ||
56 | struct rn5t618_wdt *wdt = watchdog_get_drvdata(wdt_dev); | ||
57 | int ret, i; | ||
58 | |||
59 | for (i = 0; i < ARRAY_SIZE(rn5t618_wdt_map); i++) { | ||
60 | if (rn5t618_wdt_map[i].time + 1 >= t) | ||
61 | break; | ||
62 | } | ||
63 | |||
64 | if (i == ARRAY_SIZE(rn5t618_wdt_map)) | ||
65 | return -EINVAL; | ||
66 | |||
67 | ret = regmap_update_bits(wdt->rn5t618->regmap, RN5T618_WATCHDOG, | ||
68 | RN5T618_WATCHDOG_WDOGTIM_M, | ||
69 | rn5t618_wdt_map[i].reg_val); | ||
70 | if (!ret) | ||
71 | wdt_dev->timeout = rn5t618_wdt_map[i].time; | ||
72 | |||
73 | return ret; | ||
74 | } | ||
75 | |||
76 | static int rn5t618_wdt_start(struct watchdog_device *wdt_dev) | ||
77 | { | ||
78 | struct rn5t618_wdt *wdt = watchdog_get_drvdata(wdt_dev); | ||
79 | int ret; | ||
80 | |||
81 | ret = rn5t618_wdt_set_timeout(wdt_dev, wdt_dev->timeout); | ||
82 | if (ret) | ||
83 | return ret; | ||
84 | |||
85 | /* enable repower-on */ | ||
86 | ret = regmap_update_bits(wdt->rn5t618->regmap, RN5T618_REPCNT, | ||
87 | RN5T618_REPCNT_REPWRON, | ||
88 | RN5T618_REPCNT_REPWRON); | ||
89 | if (ret) | ||
90 | return ret; | ||
91 | |||
92 | /* enable watchdog */ | ||
93 | ret = regmap_update_bits(wdt->rn5t618->regmap, RN5T618_WATCHDOG, | ||
94 | RN5T618_WATCHDOG_WDOGEN, | ||
95 | RN5T618_WATCHDOG_WDOGEN); | ||
96 | if (ret) | ||
97 | return ret; | ||
98 | |||
99 | /* enable watchdog interrupt */ | ||
100 | return regmap_update_bits(wdt->rn5t618->regmap, RN5T618_PWRIREN, | ||
101 | RN5T618_PWRIRQ_IR_WDOG, | ||
102 | RN5T618_PWRIRQ_IR_WDOG); | ||
103 | } | ||
104 | |||
105 | static int rn5t618_wdt_stop(struct watchdog_device *wdt_dev) | ||
106 | { | ||
107 | struct rn5t618_wdt *wdt = watchdog_get_drvdata(wdt_dev); | ||
108 | |||
109 | return regmap_update_bits(wdt->rn5t618->regmap, RN5T618_WATCHDOG, | ||
110 | RN5T618_WATCHDOG_WDOGEN, 0); | ||
111 | } | ||
112 | |||
113 | static int rn5t618_wdt_ping(struct watchdog_device *wdt_dev) | ||
114 | { | ||
115 | struct rn5t618_wdt *wdt = watchdog_get_drvdata(wdt_dev); | ||
116 | unsigned int val; | ||
117 | int ret; | ||
118 | |||
119 | /* The counter is restarted after a R/W access to watchdog register */ | ||
120 | ret = regmap_read(wdt->rn5t618->regmap, RN5T618_WATCHDOG, &val); | ||
121 | if (ret) | ||
122 | return ret; | ||
123 | |||
124 | ret = regmap_write(wdt->rn5t618->regmap, RN5T618_WATCHDOG, val); | ||
125 | if (ret) | ||
126 | return ret; | ||
127 | |||
128 | /* Clear pending watchdog interrupt */ | ||
129 | return regmap_update_bits(wdt->rn5t618->regmap, RN5T618_PWRIRQ, | ||
130 | RN5T618_PWRIRQ_IR_WDOG, 0); | ||
131 | } | ||
132 | |||
133 | static struct watchdog_info rn5t618_wdt_info = { | ||
134 | .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | | ||
135 | WDIOF_KEEPALIVEPING, | ||
136 | .identity = DRIVER_NAME, | ||
137 | }; | ||
138 | |||
139 | static struct watchdog_ops rn5t618_wdt_ops = { | ||
140 | .owner = THIS_MODULE, | ||
141 | .start = rn5t618_wdt_start, | ||
142 | .stop = rn5t618_wdt_stop, | ||
143 | .ping = rn5t618_wdt_ping, | ||
144 | .set_timeout = rn5t618_wdt_set_timeout, | ||
145 | }; | ||
146 | |||
147 | static int rn5t618_wdt_probe(struct platform_device *pdev) | ||
148 | { | ||
149 | struct rn5t618 *rn5t618 = dev_get_drvdata(pdev->dev.parent); | ||
150 | struct rn5t618_wdt *wdt; | ||
151 | int min_timeout, max_timeout; | ||
152 | |||
153 | wdt = devm_kzalloc(&pdev->dev, sizeof(struct rn5t618_wdt), GFP_KERNEL); | ||
154 | if (!wdt) | ||
155 | return -ENOMEM; | ||
156 | |||
157 | min_timeout = rn5t618_wdt_map[0].time; | ||
158 | max_timeout = rn5t618_wdt_map[ARRAY_SIZE(rn5t618_wdt_map) - 1].time; | ||
159 | |||
160 | wdt->rn5t618 = rn5t618; | ||
161 | wdt->wdt_dev.info = &rn5t618_wdt_info; | ||
162 | wdt->wdt_dev.ops = &rn5t618_wdt_ops; | ||
163 | wdt->wdt_dev.min_timeout = min_timeout; | ||
164 | wdt->wdt_dev.max_timeout = max_timeout; | ||
165 | wdt->wdt_dev.timeout = max_timeout; | ||
166 | wdt->wdt_dev.parent = &pdev->dev; | ||
167 | |||
168 | watchdog_set_drvdata(&wdt->wdt_dev, wdt); | ||
169 | watchdog_init_timeout(&wdt->wdt_dev, timeout, &pdev->dev); | ||
170 | watchdog_set_nowayout(&wdt->wdt_dev, nowayout); | ||
171 | |||
172 | platform_set_drvdata(pdev, wdt); | ||
173 | |||
174 | return watchdog_register_device(&wdt->wdt_dev); | ||
175 | } | ||
176 | |||
177 | static int rn5t618_wdt_remove(struct platform_device *pdev) | ||
178 | { | ||
179 | struct rn5t618_wdt *wdt = platform_get_drvdata(pdev); | ||
180 | |||
181 | watchdog_unregister_device(&wdt->wdt_dev); | ||
182 | |||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | static struct platform_driver rn5t618_wdt_driver = { | ||
187 | .probe = rn5t618_wdt_probe, | ||
188 | .remove = rn5t618_wdt_remove, | ||
189 | .driver = { | ||
190 | .name = DRIVER_NAME, | ||
191 | }, | ||
192 | }; | ||
193 | |||
194 | module_platform_driver(rn5t618_wdt_driver); | ||
195 | |||
196 | MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); | ||
197 | MODULE_DESCRIPTION("RN5T618 watchdog driver"); | ||
198 | MODULE_LICENSE("GPL v2"); | ||