1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2ff3bb2f5SLaxman Dewangan /*
3ff3bb2f5SLaxman Dewangan * Maxim MAX77620 Watchdog Driver
4ff3bb2f5SLaxman Dewangan *
5ff3bb2f5SLaxman Dewangan * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved.
6254099d8SLuca Ceresoli * Copyright (C) 2022 Luca Ceresoli
7ff3bb2f5SLaxman Dewangan *
8ff3bb2f5SLaxman Dewangan * Author: Laxman Dewangan <ldewangan@nvidia.com>
9*418c951dSLuca Ceresoli * Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
10ff3bb2f5SLaxman Dewangan */
11ff3bb2f5SLaxman Dewangan
12ff3bb2f5SLaxman Dewangan #include <linux/err.h>
13ff3bb2f5SLaxman Dewangan #include <linux/init.h>
14ff3bb2f5SLaxman Dewangan #include <linux/kernel.h>
15ff3bb2f5SLaxman Dewangan #include <linux/module.h>
16ac316725SRandy Dunlap #include <linux/mod_devicetable.h>
17ff3bb2f5SLaxman Dewangan #include <linux/mfd/max77620.h>
18254099d8SLuca Ceresoli #include <linux/mfd/max77714.h>
19ff3bb2f5SLaxman Dewangan #include <linux/platform_device.h>
20ff3bb2f5SLaxman Dewangan #include <linux/regmap.h>
21ff3bb2f5SLaxman Dewangan #include <linux/slab.h>
22ff3bb2f5SLaxman Dewangan #include <linux/watchdog.h>
23ff3bb2f5SLaxman Dewangan
24ff3bb2f5SLaxman Dewangan static bool nowayout = WATCHDOG_NOWAYOUT;
25ff3bb2f5SLaxman Dewangan
26254099d8SLuca Ceresoli /**
27254099d8SLuca Ceresoli * struct max77620_variant - Data specific to a chip variant
28254099d8SLuca Ceresoli * @wdt_info: watchdog descriptor
29254099d8SLuca Ceresoli * @reg_onoff_cnfg2: ONOFF_CNFG2 register offset
30254099d8SLuca Ceresoli * @reg_cnfg_glbl2: CNFG_GLBL2 register offset
31254099d8SLuca Ceresoli * @reg_cnfg_glbl3: CNFG_GLBL3 register offset
32254099d8SLuca Ceresoli * @wdtc_mask: WDTC bit mask in CNFG_GLBL3 (=bits to update to ping the watchdog)
33254099d8SLuca Ceresoli * @bit_wd_rst_wk: WD_RST_WK bit offset within ONOFF_CNFG2
34254099d8SLuca Ceresoli * @cnfg_glbl2_cfg_bits: configuration bits to enable in CNFG_GLBL2 register
35254099d8SLuca Ceresoli */
36254099d8SLuca Ceresoli struct max77620_variant {
37254099d8SLuca Ceresoli u8 reg_onoff_cnfg2;
38254099d8SLuca Ceresoli u8 reg_cnfg_glbl2;
39254099d8SLuca Ceresoli u8 reg_cnfg_glbl3;
40254099d8SLuca Ceresoli u8 wdtc_mask;
41254099d8SLuca Ceresoli u8 bit_wd_rst_wk;
42254099d8SLuca Ceresoli u8 cnfg_glbl2_cfg_bits;
43254099d8SLuca Ceresoli };
44254099d8SLuca Ceresoli
45ff3bb2f5SLaxman Dewangan struct max77620_wdt {
46ff3bb2f5SLaxman Dewangan struct device *dev;
47ff3bb2f5SLaxman Dewangan struct regmap *rmap;
48254099d8SLuca Ceresoli const struct max77620_variant *drv_data;
49ff3bb2f5SLaxman Dewangan struct watchdog_device wdt_dev;
50ff3bb2f5SLaxman Dewangan };
51ff3bb2f5SLaxman Dewangan
52254099d8SLuca Ceresoli static const struct max77620_variant max77620_wdt_data = {
53254099d8SLuca Ceresoli .reg_onoff_cnfg2 = MAX77620_REG_ONOFFCNFG2,
54254099d8SLuca Ceresoli .reg_cnfg_glbl2 = MAX77620_REG_CNFGGLBL2,
55254099d8SLuca Ceresoli .reg_cnfg_glbl3 = MAX77620_REG_CNFGGLBL3,
56254099d8SLuca Ceresoli .wdtc_mask = MAX77620_WDTC_MASK,
57254099d8SLuca Ceresoli .bit_wd_rst_wk = MAX77620_ONOFFCNFG2_WD_RST_WK,
58254099d8SLuca Ceresoli /* Set WDT clear in OFF and sleep mode */
59254099d8SLuca Ceresoli .cnfg_glbl2_cfg_bits = MAX77620_WDTSLPC | MAX77620_WDTOFFC,
60254099d8SLuca Ceresoli };
61254099d8SLuca Ceresoli
62254099d8SLuca Ceresoli static const struct max77620_variant max77714_wdt_data = {
63254099d8SLuca Ceresoli .reg_onoff_cnfg2 = MAX77714_CNFG2_ONOFF,
64254099d8SLuca Ceresoli .reg_cnfg_glbl2 = MAX77714_CNFG_GLBL2,
65254099d8SLuca Ceresoli .reg_cnfg_glbl3 = MAX77714_CNFG_GLBL3,
66254099d8SLuca Ceresoli .wdtc_mask = MAX77714_WDTC,
67254099d8SLuca Ceresoli .bit_wd_rst_wk = MAX77714_WD_RST_WK,
68254099d8SLuca Ceresoli /* Set WDT clear in sleep mode (there is no WDTOFFC on MAX77714) */
69254099d8SLuca Ceresoli .cnfg_glbl2_cfg_bits = MAX77714_WDTSLPC,
70254099d8SLuca Ceresoli };
71254099d8SLuca Ceresoli
max77620_wdt_start(struct watchdog_device * wdt_dev)72ff3bb2f5SLaxman Dewangan static int max77620_wdt_start(struct watchdog_device *wdt_dev)
73ff3bb2f5SLaxman Dewangan {
74ff3bb2f5SLaxman Dewangan struct max77620_wdt *wdt = watchdog_get_drvdata(wdt_dev);
75ff3bb2f5SLaxman Dewangan
76254099d8SLuca Ceresoli return regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2,
77ff3bb2f5SLaxman Dewangan MAX77620_WDTEN, MAX77620_WDTEN);
78ff3bb2f5SLaxman Dewangan }
79ff3bb2f5SLaxman Dewangan
max77620_wdt_stop(struct watchdog_device * wdt_dev)80ff3bb2f5SLaxman Dewangan static int max77620_wdt_stop(struct watchdog_device *wdt_dev)
81ff3bb2f5SLaxman Dewangan {
82ff3bb2f5SLaxman Dewangan struct max77620_wdt *wdt = watchdog_get_drvdata(wdt_dev);
83ff3bb2f5SLaxman Dewangan
84254099d8SLuca Ceresoli return regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2,
85ff3bb2f5SLaxman Dewangan MAX77620_WDTEN, 0);
86ff3bb2f5SLaxman Dewangan }
87ff3bb2f5SLaxman Dewangan
max77620_wdt_ping(struct watchdog_device * wdt_dev)88ff3bb2f5SLaxman Dewangan static int max77620_wdt_ping(struct watchdog_device *wdt_dev)
89ff3bb2f5SLaxman Dewangan {
90ff3bb2f5SLaxman Dewangan struct max77620_wdt *wdt = watchdog_get_drvdata(wdt_dev);
91ff3bb2f5SLaxman Dewangan
92254099d8SLuca Ceresoli return regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl3,
93254099d8SLuca Ceresoli wdt->drv_data->wdtc_mask, 0x1);
94ff3bb2f5SLaxman Dewangan }
95ff3bb2f5SLaxman Dewangan
max77620_wdt_set_timeout(struct watchdog_device * wdt_dev,unsigned int timeout)96ff3bb2f5SLaxman Dewangan static int max77620_wdt_set_timeout(struct watchdog_device *wdt_dev,
97ff3bb2f5SLaxman Dewangan unsigned int timeout)
98ff3bb2f5SLaxman Dewangan {
99ff3bb2f5SLaxman Dewangan struct max77620_wdt *wdt = watchdog_get_drvdata(wdt_dev);
100ff3bb2f5SLaxman Dewangan unsigned int wdt_timeout;
101ff3bb2f5SLaxman Dewangan u8 regval;
102ff3bb2f5SLaxman Dewangan int ret;
103ff3bb2f5SLaxman Dewangan
104ff3bb2f5SLaxman Dewangan switch (timeout) {
105ff3bb2f5SLaxman Dewangan case 0 ... 2:
106ff3bb2f5SLaxman Dewangan regval = MAX77620_TWD_2s;
107ff3bb2f5SLaxman Dewangan wdt_timeout = 2;
108ff3bb2f5SLaxman Dewangan break;
109ff3bb2f5SLaxman Dewangan
110ff3bb2f5SLaxman Dewangan case 3 ... 16:
111ff3bb2f5SLaxman Dewangan regval = MAX77620_TWD_16s;
112ff3bb2f5SLaxman Dewangan wdt_timeout = 16;
113ff3bb2f5SLaxman Dewangan break;
114ff3bb2f5SLaxman Dewangan
115ff3bb2f5SLaxman Dewangan case 17 ... 64:
116ff3bb2f5SLaxman Dewangan regval = MAX77620_TWD_64s;
117ff3bb2f5SLaxman Dewangan wdt_timeout = 64;
118ff3bb2f5SLaxman Dewangan break;
119ff3bb2f5SLaxman Dewangan
120ff3bb2f5SLaxman Dewangan default:
121ff3bb2f5SLaxman Dewangan regval = MAX77620_TWD_128s;
122ff3bb2f5SLaxman Dewangan wdt_timeout = 128;
123ff3bb2f5SLaxman Dewangan break;
124ff3bb2f5SLaxman Dewangan }
125ff3bb2f5SLaxman Dewangan
1263f6f1f1fSLuca Ceresoli /*
1273f6f1f1fSLuca Ceresoli * "If the value of TWD needs to be changed, clear the system
1283f6f1f1fSLuca Ceresoli * watchdog timer first [...], then change the value of TWD."
1293f6f1f1fSLuca Ceresoli * (MAX77714 datasheet but applies to MAX77620 too)
1303f6f1f1fSLuca Ceresoli */
131254099d8SLuca Ceresoli ret = regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl3,
132254099d8SLuca Ceresoli wdt->drv_data->wdtc_mask, 0x1);
133ff3bb2f5SLaxman Dewangan if (ret < 0)
134ff3bb2f5SLaxman Dewangan return ret;
135ff3bb2f5SLaxman Dewangan
136254099d8SLuca Ceresoli ret = regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2,
137ff3bb2f5SLaxman Dewangan MAX77620_TWD_MASK, regval);
138ff3bb2f5SLaxman Dewangan if (ret < 0)
139ff3bb2f5SLaxman Dewangan return ret;
140ff3bb2f5SLaxman Dewangan
141ff3bb2f5SLaxman Dewangan wdt_dev->timeout = wdt_timeout;
142ff3bb2f5SLaxman Dewangan
143ff3bb2f5SLaxman Dewangan return 0;
144ff3bb2f5SLaxman Dewangan }
145ff3bb2f5SLaxman Dewangan
146ff3bb2f5SLaxman Dewangan static const struct watchdog_info max77620_wdt_info = {
147ff3bb2f5SLaxman Dewangan .identity = "max77620-watchdog",
148ff3bb2f5SLaxman Dewangan .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
149ff3bb2f5SLaxman Dewangan };
150ff3bb2f5SLaxman Dewangan
151ff3bb2f5SLaxman Dewangan static const struct watchdog_ops max77620_wdt_ops = {
152ff3bb2f5SLaxman Dewangan .start = max77620_wdt_start,
153ff3bb2f5SLaxman Dewangan .stop = max77620_wdt_stop,
154ff3bb2f5SLaxman Dewangan .ping = max77620_wdt_ping,
155ff3bb2f5SLaxman Dewangan .set_timeout = max77620_wdt_set_timeout,
156ff3bb2f5SLaxman Dewangan };
157ff3bb2f5SLaxman Dewangan
max77620_wdt_probe(struct platform_device * pdev)158ff3bb2f5SLaxman Dewangan static int max77620_wdt_probe(struct platform_device *pdev)
159ff3bb2f5SLaxman Dewangan {
160254099d8SLuca Ceresoli const struct platform_device_id *id = platform_get_device_id(pdev);
161b6e6bf4fSGuenter Roeck struct device *dev = &pdev->dev;
162ff3bb2f5SLaxman Dewangan struct max77620_wdt *wdt;
163ff3bb2f5SLaxman Dewangan struct watchdog_device *wdt_dev;
164ff3bb2f5SLaxman Dewangan unsigned int regval;
165ff3bb2f5SLaxman Dewangan int ret;
166ff3bb2f5SLaxman Dewangan
167b6e6bf4fSGuenter Roeck wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
168ff3bb2f5SLaxman Dewangan if (!wdt)
169ff3bb2f5SLaxman Dewangan return -ENOMEM;
170ff3bb2f5SLaxman Dewangan
171b6e6bf4fSGuenter Roeck wdt->dev = dev;
172254099d8SLuca Ceresoli wdt->drv_data = (const struct max77620_variant *) id->driver_data;
173254099d8SLuca Ceresoli
174b6e6bf4fSGuenter Roeck wdt->rmap = dev_get_regmap(dev->parent, NULL);
175ff3bb2f5SLaxman Dewangan if (!wdt->rmap) {
176ff3bb2f5SLaxman Dewangan dev_err(wdt->dev, "Failed to get parent regmap\n");
177ff3bb2f5SLaxman Dewangan return -ENODEV;
178ff3bb2f5SLaxman Dewangan }
179ff3bb2f5SLaxman Dewangan
180ff3bb2f5SLaxman Dewangan wdt_dev = &wdt->wdt_dev;
181ff3bb2f5SLaxman Dewangan wdt_dev->info = &max77620_wdt_info;
182ff3bb2f5SLaxman Dewangan wdt_dev->ops = &max77620_wdt_ops;
183ff3bb2f5SLaxman Dewangan wdt_dev->min_timeout = 2;
184ff3bb2f5SLaxman Dewangan wdt_dev->max_timeout = 128;
185ff3bb2f5SLaxman Dewangan wdt_dev->max_hw_heartbeat_ms = 128 * 1000;
186ff3bb2f5SLaxman Dewangan
187ff3bb2f5SLaxman Dewangan platform_set_drvdata(pdev, wdt);
188ff3bb2f5SLaxman Dewangan
189ff3bb2f5SLaxman Dewangan /* Enable WD_RST_WK - WDT expire results in a restart */
190254099d8SLuca Ceresoli ret = regmap_update_bits(wdt->rmap, wdt->drv_data->reg_onoff_cnfg2,
191254099d8SLuca Ceresoli wdt->drv_data->bit_wd_rst_wk,
192254099d8SLuca Ceresoli wdt->drv_data->bit_wd_rst_wk);
193ff3bb2f5SLaxman Dewangan if (ret < 0) {
194ff3bb2f5SLaxman Dewangan dev_err(wdt->dev, "Failed to set WD_RST_WK: %d\n", ret);
195ff3bb2f5SLaxman Dewangan return ret;
196ff3bb2f5SLaxman Dewangan }
197ff3bb2f5SLaxman Dewangan
198254099d8SLuca Ceresoli /* Set the "auto WDT clear" bits available on the chip */
199254099d8SLuca Ceresoli ret = regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2,
200254099d8SLuca Ceresoli wdt->drv_data->cnfg_glbl2_cfg_bits,
201254099d8SLuca Ceresoli wdt->drv_data->cnfg_glbl2_cfg_bits);
202ff3bb2f5SLaxman Dewangan if (ret < 0) {
203ff3bb2f5SLaxman Dewangan dev_err(wdt->dev, "Failed to set WDT OFF mode: %d\n", ret);
204ff3bb2f5SLaxman Dewangan return ret;
205ff3bb2f5SLaxman Dewangan }
206ff3bb2f5SLaxman Dewangan
207ff3bb2f5SLaxman Dewangan /* Check if WDT running and if yes then set flags properly */
208254099d8SLuca Ceresoli ret = regmap_read(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2, ®val);
209ff3bb2f5SLaxman Dewangan if (ret < 0) {
210ff3bb2f5SLaxman Dewangan dev_err(wdt->dev, "Failed to read WDT CFG register: %d\n", ret);
211ff3bb2f5SLaxman Dewangan return ret;
212ff3bb2f5SLaxman Dewangan }
213ff3bb2f5SLaxman Dewangan
214ff3bb2f5SLaxman Dewangan switch (regval & MAX77620_TWD_MASK) {
215ff3bb2f5SLaxman Dewangan case MAX77620_TWD_2s:
216ff3bb2f5SLaxman Dewangan wdt_dev->timeout = 2;
217ff3bb2f5SLaxman Dewangan break;
218ff3bb2f5SLaxman Dewangan case MAX77620_TWD_16s:
219ff3bb2f5SLaxman Dewangan wdt_dev->timeout = 16;
220ff3bb2f5SLaxman Dewangan break;
221ff3bb2f5SLaxman Dewangan case MAX77620_TWD_64s:
222ff3bb2f5SLaxman Dewangan wdt_dev->timeout = 64;
223ff3bb2f5SLaxman Dewangan break;
224ff3bb2f5SLaxman Dewangan default:
225ff3bb2f5SLaxman Dewangan wdt_dev->timeout = 128;
226ff3bb2f5SLaxman Dewangan break;
227ff3bb2f5SLaxman Dewangan }
228ff3bb2f5SLaxman Dewangan
229ff3bb2f5SLaxman Dewangan if (regval & MAX77620_WDTEN)
230ff3bb2f5SLaxman Dewangan set_bit(WDOG_HW_RUNNING, &wdt_dev->status);
231ff3bb2f5SLaxman Dewangan
232ff3bb2f5SLaxman Dewangan watchdog_set_nowayout(wdt_dev, nowayout);
233ff3bb2f5SLaxman Dewangan watchdog_set_drvdata(wdt_dev, wdt);
234ff3bb2f5SLaxman Dewangan
235b6e6bf4fSGuenter Roeck watchdog_stop_on_unregister(wdt_dev);
2369daa2e14SWolfram Sang return devm_watchdog_register_device(dev, wdt_dev);
237ff3bb2f5SLaxman Dewangan }
238ff3bb2f5SLaxman Dewangan
239e8c7ebfdSArvind Yadav static const struct platform_device_id max77620_wdt_devtype[] = {
240254099d8SLuca Ceresoli { "max77620-watchdog", (kernel_ulong_t)&max77620_wdt_data },
241254099d8SLuca Ceresoli { "max77714-watchdog", (kernel_ulong_t)&max77714_wdt_data },
242ff3bb2f5SLaxman Dewangan { },
243ff3bb2f5SLaxman Dewangan };
244f99524dcSJavier Martinez Canillas MODULE_DEVICE_TABLE(platform, max77620_wdt_devtype);
245ff3bb2f5SLaxman Dewangan
246ff3bb2f5SLaxman Dewangan static struct platform_driver max77620_wdt_driver = {
247ff3bb2f5SLaxman Dewangan .driver = {
248ff3bb2f5SLaxman Dewangan .name = "max77620-watchdog",
249ff3bb2f5SLaxman Dewangan },
250ff3bb2f5SLaxman Dewangan .probe = max77620_wdt_probe,
251ff3bb2f5SLaxman Dewangan .id_table = max77620_wdt_devtype,
252ff3bb2f5SLaxman Dewangan };
253ff3bb2f5SLaxman Dewangan
254ff3bb2f5SLaxman Dewangan module_platform_driver(max77620_wdt_driver);
255ff3bb2f5SLaxman Dewangan
256ff3bb2f5SLaxman Dewangan MODULE_DESCRIPTION("Max77620 watchdog timer driver");
257ff3bb2f5SLaxman Dewangan
258ff3bb2f5SLaxman Dewangan module_param(nowayout, bool, 0);
259ff3bb2f5SLaxman Dewangan MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
260ff3bb2f5SLaxman Dewangan "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
261ff3bb2f5SLaxman Dewangan
262ff3bb2f5SLaxman Dewangan MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
263*418c951dSLuca Ceresoli MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>");
264ff3bb2f5SLaxman Dewangan MODULE_LICENSE("GPL v2");
265