1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (C) STMicroelectronics 2018 |
3 | // Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics. |
4 | |
5 | #include <linux/kernel.h> |
6 | #include <linux/mfd/stpmic1.h> |
7 | #include <linux/module.h> |
8 | #include <linux/platform_device.h> |
9 | #include <linux/of.h> |
10 | #include <linux/regmap.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/watchdog.h> |
13 | |
14 | /* WATCHDOG CONTROL REGISTER bit */ |
15 | #define WDT_START BIT(0) |
16 | #define WDT_PING BIT(1) |
17 | #define WDT_START_MASK BIT(0) |
18 | #define WDT_PING_MASK BIT(1) |
19 | #define WDT_STOP 0 |
20 | |
21 | #define PMIC_WDT_MIN_TIMEOUT 1 |
22 | #define PMIC_WDT_MAX_TIMEOUT 256 |
23 | #define PMIC_WDT_DEFAULT_TIMEOUT 30 |
24 | |
25 | static bool nowayout = WATCHDOG_NOWAYOUT; |
26 | module_param(nowayout, bool, 0); |
27 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
28 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
29 | |
30 | struct stpmic1_wdt { |
31 | struct stpmic1 *pmic; |
32 | struct watchdog_device wdtdev; |
33 | }; |
34 | |
35 | static int pmic_wdt_start(struct watchdog_device *wdd) |
36 | { |
37 | struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd); |
38 | |
39 | return regmap_update_bits(map: wdt->pmic->regmap, |
40 | WCHDG_CR, WDT_START_MASK, WDT_START); |
41 | } |
42 | |
43 | static int pmic_wdt_stop(struct watchdog_device *wdd) |
44 | { |
45 | struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd); |
46 | |
47 | return regmap_update_bits(map: wdt->pmic->regmap, |
48 | WCHDG_CR, WDT_START_MASK, WDT_STOP); |
49 | } |
50 | |
51 | static int pmic_wdt_ping(struct watchdog_device *wdd) |
52 | { |
53 | struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd); |
54 | |
55 | return regmap_update_bits(map: wdt->pmic->regmap, |
56 | WCHDG_CR, WDT_PING_MASK, WDT_PING); |
57 | } |
58 | |
59 | static int pmic_wdt_set_timeout(struct watchdog_device *wdd, |
60 | unsigned int timeout) |
61 | { |
62 | struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd); |
63 | |
64 | wdd->timeout = timeout; |
65 | /* timeout is equal to register value + 1 */ |
66 | return regmap_write(map: wdt->pmic->regmap, WCHDG_TIMER_CR, val: timeout - 1); |
67 | } |
68 | |
69 | static const struct watchdog_info pmic_watchdog_info = { |
70 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
71 | .identity = "STPMIC1 PMIC Watchdog" , |
72 | }; |
73 | |
74 | static const struct watchdog_ops pmic_watchdog_ops = { |
75 | .owner = THIS_MODULE, |
76 | .start = pmic_wdt_start, |
77 | .stop = pmic_wdt_stop, |
78 | .ping = pmic_wdt_ping, |
79 | .set_timeout = pmic_wdt_set_timeout, |
80 | }; |
81 | |
82 | static int pmic_wdt_probe(struct platform_device *pdev) |
83 | { |
84 | struct device *dev = &pdev->dev; |
85 | int ret; |
86 | struct stpmic1 *pmic; |
87 | struct stpmic1_wdt *wdt; |
88 | |
89 | if (!dev->parent) |
90 | return -EINVAL; |
91 | |
92 | pmic = dev_get_drvdata(dev: dev->parent); |
93 | if (!pmic) |
94 | return -EINVAL; |
95 | |
96 | wdt = devm_kzalloc(dev, size: sizeof(struct stpmic1_wdt), GFP_KERNEL); |
97 | if (!wdt) |
98 | return -ENOMEM; |
99 | |
100 | wdt->pmic = pmic; |
101 | |
102 | wdt->wdtdev.info = &pmic_watchdog_info; |
103 | wdt->wdtdev.ops = &pmic_watchdog_ops; |
104 | wdt->wdtdev.min_timeout = PMIC_WDT_MIN_TIMEOUT; |
105 | wdt->wdtdev.max_timeout = PMIC_WDT_MAX_TIMEOUT; |
106 | wdt->wdtdev.parent = dev; |
107 | |
108 | wdt->wdtdev.timeout = PMIC_WDT_DEFAULT_TIMEOUT; |
109 | watchdog_init_timeout(wdd: &wdt->wdtdev, timeout_parm: 0, dev); |
110 | |
111 | watchdog_set_nowayout(wdd: &wdt->wdtdev, nowayout); |
112 | watchdog_set_drvdata(wdd: &wdt->wdtdev, data: wdt); |
113 | |
114 | ret = devm_watchdog_register_device(dev, &wdt->wdtdev); |
115 | if (ret) |
116 | return ret; |
117 | |
118 | dev_dbg(wdt->pmic->dev, "PMIC Watchdog driver probed\n" ); |
119 | return 0; |
120 | } |
121 | |
122 | static const struct of_device_id of_pmic_wdt_match[] = { |
123 | { .compatible = "st,stpmic1-wdt" }, |
124 | { }, |
125 | }; |
126 | |
127 | MODULE_DEVICE_TABLE(of, of_pmic_wdt_match); |
128 | |
129 | static struct platform_driver stpmic1_wdt_driver = { |
130 | .probe = pmic_wdt_probe, |
131 | .driver = { |
132 | .name = "stpmic1-wdt" , |
133 | .of_match_table = of_pmic_wdt_match, |
134 | }, |
135 | }; |
136 | module_platform_driver(stpmic1_wdt_driver); |
137 | |
138 | MODULE_DESCRIPTION("Watchdog driver for STPMIC1 device" ); |
139 | MODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>" ); |
140 | MODULE_LICENSE("GPL v2" ); |
141 | |