1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * intel-mid_wdt: generic Intel MID SCU watchdog driver |
4 | * |
5 | * Platforms supported so far: |
6 | * - Merrifield only |
7 | * |
8 | * Copyright (C) 2014 Intel Corporation. All rights reserved. |
9 | * Contact: David Cohen <david.a.cohen@linux.intel.com> |
10 | */ |
11 | |
12 | #include <linux/bitops.h> |
13 | #include <linux/device.h> |
14 | #include <linux/errno.h> |
15 | #include <linux/interrupt.h> |
16 | #include <linux/math.h> |
17 | #include <linux/module.h> |
18 | #include <linux/panic.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/types.h> |
21 | #include <linux/watchdog.h> |
22 | |
23 | #include <linux/platform_data/intel-mid_wdt.h> |
24 | |
25 | #include <asm/intel_scu_ipc.h> |
26 | |
27 | #define IPC_WATCHDOG 0xf8 |
28 | |
29 | #define MID_WDT_PRETIMEOUT 15 |
30 | #define MID_WDT_TIMEOUT_MIN (1 + MID_WDT_PRETIMEOUT) |
31 | #define MID_WDT_TIMEOUT_MAX 170 |
32 | #define MID_WDT_DEFAULT_TIMEOUT 90 |
33 | |
34 | /* SCU watchdog messages */ |
35 | enum { |
36 | SCU_WATCHDOG_START = 0, |
37 | SCU_WATCHDOG_STOP, |
38 | SCU_WATCHDOG_KEEPALIVE, |
39 | }; |
40 | |
41 | struct mid_wdt { |
42 | struct watchdog_device wd; |
43 | struct device *dev; |
44 | struct intel_scu_ipc_dev *scu; |
45 | }; |
46 | |
47 | static inline int |
48 | wdt_command(struct mid_wdt *mid, int sub, const void *in, size_t inlen, size_t size) |
49 | { |
50 | struct intel_scu_ipc_dev *scu = mid->scu; |
51 | |
52 | return intel_scu_ipc_dev_command_with_size(scu, IPC_WATCHDOG, sub, in, |
53 | inlen, size, NULL, outlen: 0); |
54 | } |
55 | |
56 | static int wdt_start(struct watchdog_device *wd) |
57 | { |
58 | struct mid_wdt *mid = watchdog_get_drvdata(wdd: wd); |
59 | int ret, in_size; |
60 | int timeout = wd->timeout; |
61 | struct ipc_wd_start { |
62 | u32 pretimeout; |
63 | u32 timeout; |
64 | } ipc_wd_start = { .pretimeout: timeout - MID_WDT_PRETIMEOUT, .timeout: timeout }; |
65 | |
66 | /* |
67 | * SCU expects the input size for watchdog IPC to be 2 which is the |
68 | * size of the structure in dwords. SCU IPC normally takes bytes |
69 | * but this is a special case where we specify size to be different |
70 | * than inlen. |
71 | */ |
72 | in_size = DIV_ROUND_UP(sizeof(ipc_wd_start), 4); |
73 | |
74 | ret = wdt_command(mid, sub: SCU_WATCHDOG_START, in: &ipc_wd_start, |
75 | inlen: sizeof(ipc_wd_start), size: in_size); |
76 | if (ret) |
77 | dev_crit(mid->dev, "error starting watchdog: %d\n" , ret); |
78 | |
79 | return ret; |
80 | } |
81 | |
82 | static int wdt_ping(struct watchdog_device *wd) |
83 | { |
84 | struct mid_wdt *mid = watchdog_get_drvdata(wdd: wd); |
85 | int ret; |
86 | |
87 | ret = wdt_command(mid, sub: SCU_WATCHDOG_KEEPALIVE, NULL, inlen: 0, size: 0); |
88 | if (ret) |
89 | dev_crit(mid->dev, "Error executing keepalive: %d\n" , ret); |
90 | |
91 | return ret; |
92 | } |
93 | |
94 | static int wdt_stop(struct watchdog_device *wd) |
95 | { |
96 | struct mid_wdt *mid = watchdog_get_drvdata(wdd: wd); |
97 | int ret; |
98 | |
99 | ret = wdt_command(mid, sub: SCU_WATCHDOG_STOP, NULL, inlen: 0, size: 0); |
100 | if (ret) |
101 | dev_crit(mid->dev, "Error stopping watchdog: %d\n" , ret); |
102 | |
103 | return ret; |
104 | } |
105 | |
106 | static irqreturn_t mid_wdt_irq(int irq, void *dev_id) |
107 | { |
108 | panic(fmt: "Kernel Watchdog" ); |
109 | |
110 | /* This code should not be reached */ |
111 | return IRQ_HANDLED; |
112 | } |
113 | |
114 | static const struct watchdog_info mid_wdt_info = { |
115 | .identity = "Intel MID SCU watchdog" , |
116 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, |
117 | }; |
118 | |
119 | static const struct watchdog_ops mid_wdt_ops = { |
120 | .owner = THIS_MODULE, |
121 | .start = wdt_start, |
122 | .stop = wdt_stop, |
123 | .ping = wdt_ping, |
124 | }; |
125 | |
126 | static int mid_wdt_probe(struct platform_device *pdev) |
127 | { |
128 | struct device *dev = &pdev->dev; |
129 | struct watchdog_device *wdt_dev; |
130 | struct intel_mid_wdt_pdata *pdata = dev_get_platdata(dev); |
131 | struct mid_wdt *mid; |
132 | int ret; |
133 | |
134 | if (!pdata) { |
135 | dev_err(dev, "missing platform data\n" ); |
136 | return -EINVAL; |
137 | } |
138 | |
139 | if (pdata->probe) { |
140 | ret = pdata->probe(pdev); |
141 | if (ret) |
142 | return ret; |
143 | } |
144 | |
145 | mid = devm_kzalloc(dev, size: sizeof(*mid), GFP_KERNEL); |
146 | if (!mid) |
147 | return -ENOMEM; |
148 | |
149 | mid->dev = dev; |
150 | wdt_dev = &mid->wd; |
151 | |
152 | wdt_dev->info = &mid_wdt_info; |
153 | wdt_dev->ops = &mid_wdt_ops; |
154 | wdt_dev->min_timeout = MID_WDT_TIMEOUT_MIN; |
155 | wdt_dev->max_timeout = MID_WDT_TIMEOUT_MAX; |
156 | wdt_dev->timeout = MID_WDT_DEFAULT_TIMEOUT; |
157 | wdt_dev->parent = dev; |
158 | |
159 | watchdog_set_nowayout(wdd: wdt_dev, WATCHDOG_NOWAYOUT); |
160 | watchdog_set_drvdata(wdd: wdt_dev, data: mid); |
161 | |
162 | mid->scu = devm_intel_scu_ipc_dev_get(dev); |
163 | if (!mid->scu) |
164 | return -EPROBE_DEFER; |
165 | |
166 | ret = devm_request_irq(dev, irq: pdata->irq, handler: mid_wdt_irq, |
167 | IRQF_SHARED | IRQF_NO_SUSPEND, devname: "watchdog" , |
168 | dev_id: wdt_dev); |
169 | if (ret) { |
170 | dev_err(dev, "error requesting warning irq %d\n" , pdata->irq); |
171 | return ret; |
172 | } |
173 | |
174 | /* |
175 | * The firmware followed by U-Boot leaves the watchdog running |
176 | * with the default threshold which may vary. When we get here |
177 | * we should make a decision to prevent any side effects before |
178 | * user space daemon will take care of it. The best option, |
179 | * taking into consideration that there is no way to read values |
180 | * back from hardware, is to enforce watchdog being run with |
181 | * deterministic values. |
182 | */ |
183 | ret = wdt_start(wd: wdt_dev); |
184 | if (ret) |
185 | return ret; |
186 | |
187 | /* Make sure the watchdog is serviced */ |
188 | set_bit(WDOG_HW_RUNNING, addr: &wdt_dev->status); |
189 | |
190 | ret = devm_watchdog_register_device(dev, wdt_dev); |
191 | if (ret) |
192 | return ret; |
193 | |
194 | dev_info(dev, "Intel MID watchdog device probed\n" ); |
195 | |
196 | return 0; |
197 | } |
198 | |
199 | static struct platform_driver mid_wdt_driver = { |
200 | .probe = mid_wdt_probe, |
201 | .driver = { |
202 | .name = "intel_mid_wdt" , |
203 | }, |
204 | }; |
205 | |
206 | module_platform_driver(mid_wdt_driver); |
207 | |
208 | MODULE_AUTHOR("David Cohen <david.a.cohen@linux.intel.com>" ); |
209 | MODULE_DESCRIPTION("Watchdog Driver for Intel MID platform" ); |
210 | MODULE_LICENSE("GPL" ); |
211 | MODULE_ALIAS("platform:intel_mid_wdt" ); |
212 | |