1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun // Copyright (C) 2018 ROHM Semiconductors
3*4882a593Smuzhiyun // ROHM BD70528MWV watchdog driver
4*4882a593Smuzhiyun
5*4882a593Smuzhiyun #include <linux/bcd.h>
6*4882a593Smuzhiyun #include <linux/kernel.h>
7*4882a593Smuzhiyun #include <linux/mfd/rohm-bd70528.h>
8*4882a593Smuzhiyun #include <linux/module.h>
9*4882a593Smuzhiyun #include <linux/of.h>
10*4882a593Smuzhiyun #include <linux/platform_device.h>
11*4882a593Smuzhiyun #include <linux/regmap.h>
12*4882a593Smuzhiyun #include <linux/watchdog.h>
13*4882a593Smuzhiyun
14*4882a593Smuzhiyun /*
15*4882a593Smuzhiyun * Max time we can set is 1 hour, 59 minutes and 59 seconds
16*4882a593Smuzhiyun * and Minimum time is 1 second
17*4882a593Smuzhiyun */
18*4882a593Smuzhiyun #define WDT_MAX_MS ((2 * 60 * 60 - 1) * 1000)
19*4882a593Smuzhiyun #define WDT_MIN_MS 1000
20*4882a593Smuzhiyun #define DEFAULT_TIMEOUT 60
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun #define WD_CTRL_MAGIC1 0x55
23*4882a593Smuzhiyun #define WD_CTRL_MAGIC2 0xAA
24*4882a593Smuzhiyun
25*4882a593Smuzhiyun struct wdtbd70528 {
26*4882a593Smuzhiyun struct device *dev;
27*4882a593Smuzhiyun struct regmap *regmap;
28*4882a593Smuzhiyun struct rohm_regmap_dev *mfd;
29*4882a593Smuzhiyun struct watchdog_device wdt;
30*4882a593Smuzhiyun };
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun /**
33*4882a593Smuzhiyun * bd70528_wdt_set - arm or disarm watchdog timer
34*4882a593Smuzhiyun *
35*4882a593Smuzhiyun * @data: device data for the PMIC instance we want to operate on
36*4882a593Smuzhiyun * @enable: new state of WDT. zero to disable, non zero to enable
37*4882a593Smuzhiyun * @old_state: previous state of WDT will be filled here
38*4882a593Smuzhiyun *
39*4882a593Smuzhiyun * Arm or disarm WDT on BD70528 PMIC. Expected to be called only by
40*4882a593Smuzhiyun * BD70528 RTC and BD70528 WDT drivers. The rtc_timer_lock must be taken
41*4882a593Smuzhiyun * by calling bd70528_wdt_lock before calling bd70528_wdt_set.
42*4882a593Smuzhiyun */
bd70528_wdt_set(struct rohm_regmap_dev * data,int enable,int * old_state)43*4882a593Smuzhiyun int bd70528_wdt_set(struct rohm_regmap_dev *data, int enable, int *old_state)
44*4882a593Smuzhiyun {
45*4882a593Smuzhiyun int ret, i;
46*4882a593Smuzhiyun unsigned int tmp;
47*4882a593Smuzhiyun struct bd70528_data *bd70528 = container_of(data, struct bd70528_data,
48*4882a593Smuzhiyun chip);
49*4882a593Smuzhiyun u8 wd_ctrl_arr[3] = { WD_CTRL_MAGIC1, WD_CTRL_MAGIC2, 0 };
50*4882a593Smuzhiyun u8 *wd_ctrl = &wd_ctrl_arr[2];
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun ret = regmap_read(bd70528->chip.regmap, BD70528_REG_WDT_CTRL, &tmp);
53*4882a593Smuzhiyun if (ret)
54*4882a593Smuzhiyun return ret;
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun *wd_ctrl = (u8)tmp;
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun if (old_state) {
59*4882a593Smuzhiyun if (*wd_ctrl & BD70528_MASK_WDT_EN)
60*4882a593Smuzhiyun *old_state |= BD70528_WDT_STATE_BIT;
61*4882a593Smuzhiyun else
62*4882a593Smuzhiyun *old_state &= ~BD70528_WDT_STATE_BIT;
63*4882a593Smuzhiyun if ((!enable) == (!(*old_state & BD70528_WDT_STATE_BIT)))
64*4882a593Smuzhiyun return 0;
65*4882a593Smuzhiyun }
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun if (enable) {
68*4882a593Smuzhiyun if (*wd_ctrl & BD70528_MASK_WDT_EN)
69*4882a593Smuzhiyun return 0;
70*4882a593Smuzhiyun *wd_ctrl |= BD70528_MASK_WDT_EN;
71*4882a593Smuzhiyun } else {
72*4882a593Smuzhiyun if (*wd_ctrl & BD70528_MASK_WDT_EN)
73*4882a593Smuzhiyun *wd_ctrl &= ~BD70528_MASK_WDT_EN;
74*4882a593Smuzhiyun else
75*4882a593Smuzhiyun return 0;
76*4882a593Smuzhiyun }
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun for (i = 0; i < 3; i++) {
79*4882a593Smuzhiyun ret = regmap_write(bd70528->chip.regmap, BD70528_REG_WDT_CTRL,
80*4882a593Smuzhiyun wd_ctrl_arr[i]);
81*4882a593Smuzhiyun if (ret)
82*4882a593Smuzhiyun return ret;
83*4882a593Smuzhiyun }
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun ret = regmap_read(bd70528->chip.regmap, BD70528_REG_WDT_CTRL, &tmp);
86*4882a593Smuzhiyun if ((tmp & BD70528_MASK_WDT_EN) != (*wd_ctrl & BD70528_MASK_WDT_EN)) {
87*4882a593Smuzhiyun dev_err(bd70528->chip.dev,
88*4882a593Smuzhiyun "Watchdog ctrl mismatch (hw) 0x%x (set) 0x%x\n",
89*4882a593Smuzhiyun tmp, *wd_ctrl);
90*4882a593Smuzhiyun ret = -EIO;
91*4882a593Smuzhiyun }
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun return ret;
94*4882a593Smuzhiyun }
95*4882a593Smuzhiyun EXPORT_SYMBOL(bd70528_wdt_set);
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun /**
98*4882a593Smuzhiyun * bd70528_wdt_lock - take WDT lock
99*4882a593Smuzhiyun *
100*4882a593Smuzhiyun * @data: device data for the PMIC instance we want to operate on
101*4882a593Smuzhiyun *
102*4882a593Smuzhiyun * Lock WDT for arming/disarming in order to avoid race condition caused
103*4882a593Smuzhiyun * by WDT state changes initiated by WDT and RTC drivers.
104*4882a593Smuzhiyun */
bd70528_wdt_lock(struct rohm_regmap_dev * data)105*4882a593Smuzhiyun void bd70528_wdt_lock(struct rohm_regmap_dev *data)
106*4882a593Smuzhiyun {
107*4882a593Smuzhiyun struct bd70528_data *bd70528 = container_of(data, struct bd70528_data,
108*4882a593Smuzhiyun chip);
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun mutex_lock(&bd70528->rtc_timer_lock);
111*4882a593Smuzhiyun }
112*4882a593Smuzhiyun EXPORT_SYMBOL(bd70528_wdt_lock);
113*4882a593Smuzhiyun
114*4882a593Smuzhiyun /**
115*4882a593Smuzhiyun * bd70528_wdt_unlock - unlock WDT lock
116*4882a593Smuzhiyun *
117*4882a593Smuzhiyun * @data: device data for the PMIC instance we want to operate on
118*4882a593Smuzhiyun *
119*4882a593Smuzhiyun * Unlock WDT lock which has previously been taken by call to
120*4882a593Smuzhiyun * bd70528_wdt_lock.
121*4882a593Smuzhiyun */
bd70528_wdt_unlock(struct rohm_regmap_dev * data)122*4882a593Smuzhiyun void bd70528_wdt_unlock(struct rohm_regmap_dev *data)
123*4882a593Smuzhiyun {
124*4882a593Smuzhiyun struct bd70528_data *bd70528 = container_of(data, struct bd70528_data,
125*4882a593Smuzhiyun chip);
126*4882a593Smuzhiyun
127*4882a593Smuzhiyun mutex_unlock(&bd70528->rtc_timer_lock);
128*4882a593Smuzhiyun }
129*4882a593Smuzhiyun EXPORT_SYMBOL(bd70528_wdt_unlock);
130*4882a593Smuzhiyun
bd70528_wdt_set_locked(struct wdtbd70528 * w,int enable)131*4882a593Smuzhiyun static int bd70528_wdt_set_locked(struct wdtbd70528 *w, int enable)
132*4882a593Smuzhiyun {
133*4882a593Smuzhiyun return bd70528_wdt_set(w->mfd, enable, NULL);
134*4882a593Smuzhiyun }
135*4882a593Smuzhiyun
bd70528_wdt_change(struct wdtbd70528 * w,int enable)136*4882a593Smuzhiyun static int bd70528_wdt_change(struct wdtbd70528 *w, int enable)
137*4882a593Smuzhiyun {
138*4882a593Smuzhiyun int ret;
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun bd70528_wdt_lock(w->mfd);
141*4882a593Smuzhiyun ret = bd70528_wdt_set_locked(w, enable);
142*4882a593Smuzhiyun bd70528_wdt_unlock(w->mfd);
143*4882a593Smuzhiyun
144*4882a593Smuzhiyun return ret;
145*4882a593Smuzhiyun }
146*4882a593Smuzhiyun
bd70528_wdt_start(struct watchdog_device * wdt)147*4882a593Smuzhiyun static int bd70528_wdt_start(struct watchdog_device *wdt)
148*4882a593Smuzhiyun {
149*4882a593Smuzhiyun struct wdtbd70528 *w = watchdog_get_drvdata(wdt);
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun dev_dbg(w->dev, "WDT ping...\n");
152*4882a593Smuzhiyun return bd70528_wdt_change(w, 1);
153*4882a593Smuzhiyun }
154*4882a593Smuzhiyun
bd70528_wdt_stop(struct watchdog_device * wdt)155*4882a593Smuzhiyun static int bd70528_wdt_stop(struct watchdog_device *wdt)
156*4882a593Smuzhiyun {
157*4882a593Smuzhiyun struct wdtbd70528 *w = watchdog_get_drvdata(wdt);
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun dev_dbg(w->dev, "WDT stopping...\n");
160*4882a593Smuzhiyun return bd70528_wdt_change(w, 0);
161*4882a593Smuzhiyun }
162*4882a593Smuzhiyun
bd70528_wdt_set_timeout(struct watchdog_device * wdt,unsigned int timeout)163*4882a593Smuzhiyun static int bd70528_wdt_set_timeout(struct watchdog_device *wdt,
164*4882a593Smuzhiyun unsigned int timeout)
165*4882a593Smuzhiyun {
166*4882a593Smuzhiyun unsigned int hours;
167*4882a593Smuzhiyun unsigned int minutes;
168*4882a593Smuzhiyun unsigned int seconds;
169*4882a593Smuzhiyun int ret;
170*4882a593Smuzhiyun struct wdtbd70528 *w = watchdog_get_drvdata(wdt);
171*4882a593Smuzhiyun
172*4882a593Smuzhiyun seconds = timeout;
173*4882a593Smuzhiyun hours = timeout / (60 * 60);
174*4882a593Smuzhiyun /* Maximum timeout is 1h 59m 59s => hours is 1 or 0 */
175*4882a593Smuzhiyun if (hours)
176*4882a593Smuzhiyun seconds -= (60 * 60);
177*4882a593Smuzhiyun minutes = seconds / 60;
178*4882a593Smuzhiyun seconds = seconds % 60;
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun bd70528_wdt_lock(w->mfd);
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun ret = bd70528_wdt_set_locked(w, 0);
183*4882a593Smuzhiyun if (ret)
184*4882a593Smuzhiyun goto out_unlock;
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun ret = regmap_update_bits(w->regmap, BD70528_REG_WDT_HOUR,
187*4882a593Smuzhiyun BD70528_MASK_WDT_HOUR, hours);
188*4882a593Smuzhiyun if (ret) {
189*4882a593Smuzhiyun dev_err(w->dev, "Failed to set WDT hours\n");
190*4882a593Smuzhiyun goto out_en_unlock;
191*4882a593Smuzhiyun }
192*4882a593Smuzhiyun ret = regmap_update_bits(w->regmap, BD70528_REG_WDT_MINUTE,
193*4882a593Smuzhiyun BD70528_MASK_WDT_MINUTE, bin2bcd(minutes));
194*4882a593Smuzhiyun if (ret) {
195*4882a593Smuzhiyun dev_err(w->dev, "Failed to set WDT minutes\n");
196*4882a593Smuzhiyun goto out_en_unlock;
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun ret = regmap_update_bits(w->regmap, BD70528_REG_WDT_SEC,
199*4882a593Smuzhiyun BD70528_MASK_WDT_SEC, bin2bcd(seconds));
200*4882a593Smuzhiyun if (ret)
201*4882a593Smuzhiyun dev_err(w->dev, "Failed to set WDT seconds\n");
202*4882a593Smuzhiyun else
203*4882a593Smuzhiyun dev_dbg(w->dev, "WDT tmo set to %u\n", timeout);
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun out_en_unlock:
206*4882a593Smuzhiyun ret = bd70528_wdt_set_locked(w, 1);
207*4882a593Smuzhiyun out_unlock:
208*4882a593Smuzhiyun bd70528_wdt_unlock(w->mfd);
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun return ret;
211*4882a593Smuzhiyun }
212*4882a593Smuzhiyun
213*4882a593Smuzhiyun static const struct watchdog_info bd70528_wdt_info = {
214*4882a593Smuzhiyun .identity = "bd70528-wdt",
215*4882a593Smuzhiyun .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
216*4882a593Smuzhiyun };
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun static const struct watchdog_ops bd70528_wdt_ops = {
219*4882a593Smuzhiyun .start = bd70528_wdt_start,
220*4882a593Smuzhiyun .stop = bd70528_wdt_stop,
221*4882a593Smuzhiyun .set_timeout = bd70528_wdt_set_timeout,
222*4882a593Smuzhiyun };
223*4882a593Smuzhiyun
bd70528_wdt_probe(struct platform_device * pdev)224*4882a593Smuzhiyun static int bd70528_wdt_probe(struct platform_device *pdev)
225*4882a593Smuzhiyun {
226*4882a593Smuzhiyun struct rohm_regmap_dev *bd70528;
227*4882a593Smuzhiyun struct wdtbd70528 *w;
228*4882a593Smuzhiyun int ret;
229*4882a593Smuzhiyun unsigned int reg;
230*4882a593Smuzhiyun
231*4882a593Smuzhiyun bd70528 = dev_get_drvdata(pdev->dev.parent);
232*4882a593Smuzhiyun if (!bd70528) {
233*4882a593Smuzhiyun dev_err(&pdev->dev, "No MFD driver data\n");
234*4882a593Smuzhiyun return -EINVAL;
235*4882a593Smuzhiyun }
236*4882a593Smuzhiyun w = devm_kzalloc(&pdev->dev, sizeof(*w), GFP_KERNEL);
237*4882a593Smuzhiyun if (!w)
238*4882a593Smuzhiyun return -ENOMEM;
239*4882a593Smuzhiyun
240*4882a593Smuzhiyun w->regmap = bd70528->regmap;
241*4882a593Smuzhiyun w->mfd = bd70528;
242*4882a593Smuzhiyun w->dev = &pdev->dev;
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun w->wdt.info = &bd70528_wdt_info;
245*4882a593Smuzhiyun w->wdt.ops = &bd70528_wdt_ops;
246*4882a593Smuzhiyun w->wdt.min_hw_heartbeat_ms = WDT_MIN_MS;
247*4882a593Smuzhiyun w->wdt.max_hw_heartbeat_ms = WDT_MAX_MS;
248*4882a593Smuzhiyun w->wdt.parent = pdev->dev.parent;
249*4882a593Smuzhiyun w->wdt.timeout = DEFAULT_TIMEOUT;
250*4882a593Smuzhiyun watchdog_set_drvdata(&w->wdt, w);
251*4882a593Smuzhiyun watchdog_init_timeout(&w->wdt, 0, pdev->dev.parent);
252*4882a593Smuzhiyun
253*4882a593Smuzhiyun ret = bd70528_wdt_set_timeout(&w->wdt, w->wdt.timeout);
254*4882a593Smuzhiyun if (ret) {
255*4882a593Smuzhiyun dev_err(&pdev->dev, "Failed to set the watchdog timeout\n");
256*4882a593Smuzhiyun return ret;
257*4882a593Smuzhiyun }
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun bd70528_wdt_lock(w->mfd);
260*4882a593Smuzhiyun ret = regmap_read(w->regmap, BD70528_REG_WDT_CTRL, ®);
261*4882a593Smuzhiyun bd70528_wdt_unlock(w->mfd);
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun if (ret) {
264*4882a593Smuzhiyun dev_err(&pdev->dev, "Failed to get the watchdog state\n");
265*4882a593Smuzhiyun return ret;
266*4882a593Smuzhiyun }
267*4882a593Smuzhiyun if (reg & BD70528_MASK_WDT_EN) {
268*4882a593Smuzhiyun dev_dbg(&pdev->dev, "watchdog was running during probe\n");
269*4882a593Smuzhiyun set_bit(WDOG_HW_RUNNING, &w->wdt.status);
270*4882a593Smuzhiyun }
271*4882a593Smuzhiyun
272*4882a593Smuzhiyun ret = devm_watchdog_register_device(&pdev->dev, &w->wdt);
273*4882a593Smuzhiyun if (ret < 0)
274*4882a593Smuzhiyun dev_err(&pdev->dev, "watchdog registration failed: %d\n", ret);
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun return ret;
277*4882a593Smuzhiyun }
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun static struct platform_driver bd70528_wdt = {
280*4882a593Smuzhiyun .driver = {
281*4882a593Smuzhiyun .name = "bd70528-wdt"
282*4882a593Smuzhiyun },
283*4882a593Smuzhiyun .probe = bd70528_wdt_probe,
284*4882a593Smuzhiyun };
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun module_platform_driver(bd70528_wdt);
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
289*4882a593Smuzhiyun MODULE_DESCRIPTION("BD70528 watchdog driver");
290*4882a593Smuzhiyun MODULE_LICENSE("GPL");
291*4882a593Smuzhiyun MODULE_ALIAS("platform:bd70528-wdt");
292