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