1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * System monitoring driver for DA9055 PMICs. |
4 | * |
5 | * Copyright(c) 2012 Dialog Semiconductor Ltd. |
6 | * |
7 | * Author: David Dajun Chen <dchen@diasemi.com> |
8 | * |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/types.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/watchdog.h> |
17 | #include <linux/delay.h> |
18 | |
19 | #include <linux/mfd/da9055/core.h> |
20 | #include <linux/mfd/da9055/reg.h> |
21 | |
22 | static bool nowayout = WATCHDOG_NOWAYOUT; |
23 | module_param(nowayout, bool, 0); |
24 | MODULE_PARM_DESC(nowayout, |
25 | "Watchdog cannot be stopped once started (default=" |
26 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
27 | |
28 | #define DA9055_DEF_TIMEOUT 4 |
29 | #define DA9055_TWDMIN 256 |
30 | |
31 | struct da9055_wdt_data { |
32 | struct watchdog_device wdt; |
33 | struct da9055 *da9055; |
34 | }; |
35 | |
36 | static const struct { |
37 | u8 reg_val; |
38 | int user_time; /* In seconds */ |
39 | } da9055_wdt_maps[] = { |
40 | { 0, 0 }, |
41 | { 1, 2 }, |
42 | { 2, 4 }, |
43 | { 3, 8 }, |
44 | { 4, 16 }, |
45 | { 5, 32 }, |
46 | { 5, 33 }, /* Actual time 32.768s so included both 32s and 33s */ |
47 | { 6, 65 }, |
48 | { 6, 66 }, /* Actual time 65.536s so include both, 65s and 66s */ |
49 | { 7, 131 }, |
50 | }; |
51 | |
52 | static int da9055_wdt_set_timeout(struct watchdog_device *wdt_dev, |
53 | unsigned int timeout) |
54 | { |
55 | struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdd: wdt_dev); |
56 | struct da9055 *da9055 = driver_data->da9055; |
57 | int ret, i; |
58 | |
59 | for (i = 0; i < ARRAY_SIZE(da9055_wdt_maps); i++) |
60 | if (da9055_wdt_maps[i].user_time == timeout) |
61 | break; |
62 | |
63 | if (i == ARRAY_SIZE(da9055_wdt_maps)) |
64 | ret = -EINVAL; |
65 | else |
66 | ret = da9055_reg_update(da9055, DA9055_REG_CONTROL_B, |
67 | DA9055_TWDSCALE_MASK, |
68 | reg_val: da9055_wdt_maps[i].reg_val << |
69 | DA9055_TWDSCALE_SHIFT); |
70 | if (ret < 0) { |
71 | dev_err(da9055->dev, |
72 | "Failed to update timescale bit, %d\n" , ret); |
73 | return ret; |
74 | } |
75 | |
76 | wdt_dev->timeout = timeout; |
77 | |
78 | return 0; |
79 | } |
80 | |
81 | static int da9055_wdt_ping(struct watchdog_device *wdt_dev) |
82 | { |
83 | struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdd: wdt_dev); |
84 | struct da9055 *da9055 = driver_data->da9055; |
85 | |
86 | /* |
87 | * We have a minimum time for watchdog window called TWDMIN. A write |
88 | * to the watchdog before this elapsed time will cause an error. |
89 | */ |
90 | mdelay(DA9055_TWDMIN); |
91 | |
92 | /* Reset the watchdog timer */ |
93 | return da9055_reg_update(da9055, DA9055_REG_CONTROL_E, |
94 | DA9055_WATCHDOG_MASK, reg_val: 1); |
95 | } |
96 | |
97 | static int da9055_wdt_start(struct watchdog_device *wdt_dev) |
98 | { |
99 | return da9055_wdt_set_timeout(wdt_dev, timeout: wdt_dev->timeout); |
100 | } |
101 | |
102 | static int da9055_wdt_stop(struct watchdog_device *wdt_dev) |
103 | { |
104 | return da9055_wdt_set_timeout(wdt_dev, timeout: 0); |
105 | } |
106 | |
107 | static const struct watchdog_info da9055_wdt_info = { |
108 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, |
109 | .identity = "DA9055 Watchdog" , |
110 | }; |
111 | |
112 | static const struct watchdog_ops da9055_wdt_ops = { |
113 | .owner = THIS_MODULE, |
114 | .start = da9055_wdt_start, |
115 | .stop = da9055_wdt_stop, |
116 | .ping = da9055_wdt_ping, |
117 | .set_timeout = da9055_wdt_set_timeout, |
118 | }; |
119 | |
120 | static int da9055_wdt_probe(struct platform_device *pdev) |
121 | { |
122 | struct device *dev = &pdev->dev; |
123 | struct da9055 *da9055 = dev_get_drvdata(dev: dev->parent); |
124 | struct da9055_wdt_data *driver_data; |
125 | struct watchdog_device *da9055_wdt; |
126 | int ret; |
127 | |
128 | driver_data = devm_kzalloc(dev, size: sizeof(*driver_data), GFP_KERNEL); |
129 | if (!driver_data) |
130 | return -ENOMEM; |
131 | |
132 | driver_data->da9055 = da9055; |
133 | |
134 | da9055_wdt = &driver_data->wdt; |
135 | |
136 | da9055_wdt->timeout = DA9055_DEF_TIMEOUT; |
137 | da9055_wdt->info = &da9055_wdt_info; |
138 | da9055_wdt->ops = &da9055_wdt_ops; |
139 | da9055_wdt->parent = dev; |
140 | watchdog_set_nowayout(wdd: da9055_wdt, nowayout); |
141 | watchdog_set_drvdata(wdd: da9055_wdt, data: driver_data); |
142 | |
143 | ret = da9055_wdt_stop(wdt_dev: da9055_wdt); |
144 | if (ret < 0) { |
145 | dev_err(dev, "Failed to stop watchdog, %d\n" , ret); |
146 | return ret; |
147 | } |
148 | |
149 | ret = devm_watchdog_register_device(dev, &driver_data->wdt); |
150 | if (ret != 0) |
151 | dev_err(da9055->dev, "watchdog_register_device() failed: %d\n" , |
152 | ret); |
153 | |
154 | return ret; |
155 | } |
156 | |
157 | static struct platform_driver da9055_wdt_driver = { |
158 | .probe = da9055_wdt_probe, |
159 | .driver = { |
160 | .name = "da9055-watchdog" , |
161 | }, |
162 | }; |
163 | |
164 | module_platform_driver(da9055_wdt_driver); |
165 | |
166 | MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>" ); |
167 | MODULE_DESCRIPTION("DA9055 watchdog" ); |
168 | MODULE_LICENSE("GPL" ); |
169 | MODULE_ALIAS("platform:da9055-watchdog" ); |
170 | |