1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Siemens SIMATIC IPC driver for Watchdogs |
4 | * |
5 | * Copyright (c) Siemens AG, 2020-2021 |
6 | * |
7 | * Authors: |
8 | * Gerd Haeussler <gerd.haeussler.ext@siemens.com> |
9 | */ |
10 | |
11 | #include <linux/device.h> |
12 | #include <linux/errno.h> |
13 | #include <linux/init.h> |
14 | #include <linux/io.h> |
15 | #include <linux/ioport.h> |
16 | #include <linux/kernel.h> |
17 | #include <linux/module.h> |
18 | #include <linux/pci.h> |
19 | #include <linux/platform_data/x86/p2sb.h> |
20 | #include <linux/platform_data/x86/simatic-ipc-base.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/sizes.h> |
23 | #include <linux/util_macros.h> |
24 | #include <linux/watchdog.h> |
25 | |
26 | #define WD_ENABLE_IOADR 0x62 |
27 | #define WD_TRIGGER_IOADR 0x66 |
28 | #define 0xaf |
29 | #define PAD_CFG_DW0_GPP_A_23 0x4b8 |
30 | #define SAFE_EN_N_427E 0x01 |
31 | #define SAFE_EN_N_227E 0x04 |
32 | #define WD_ENABLED 0x01 |
33 | #define WD_TRIGGERED 0x80 |
34 | #define WD_MACROMODE 0x02 |
35 | |
36 | #define TIMEOUT_MIN 2 |
37 | #define TIMEOUT_DEF 64 |
38 | #define TIMEOUT_MAX 64 |
39 | |
40 | #define GP_STATUS_REG_227E 0x404D /* IO PORT for SAFE_EN_N on 227E */ |
41 | |
42 | static bool nowayout = WATCHDOG_NOWAYOUT; |
43 | module_param(nowayout, bool, 0000); |
44 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
45 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
46 | |
47 | static struct resource gp_status_reg_227e_res = |
48 | DEFINE_RES_IO_NAMED(GP_STATUS_REG_227E, SZ_1, KBUILD_MODNAME); |
49 | |
50 | static struct resource io_resource_enable = |
51 | DEFINE_RES_IO_NAMED(WD_ENABLE_IOADR, SZ_1, |
52 | KBUILD_MODNAME " WD_ENABLE_IOADR" ); |
53 | |
54 | static struct resource io_resource_trigger = |
55 | DEFINE_RES_IO_NAMED(WD_TRIGGER_IOADR, SZ_1, |
56 | KBUILD_MODNAME " WD_TRIGGER_IOADR" ); |
57 | |
58 | /* the actual start will be discovered with p2sb, 0 is a placeholder */ |
59 | static struct resource mem_resource = |
60 | DEFINE_RES_MEM_NAMED(0, 0, "WD_RESET_BASE_ADR" ); |
61 | |
62 | static u32 wd_timeout_table[] = {2, 4, 6, 8, 16, 32, 48, 64 }; |
63 | static void __iomem *wd_reset_base_addr; |
64 | |
65 | static int wd_start(struct watchdog_device *wdd) |
66 | { |
67 | outb(inb(WD_ENABLE_IOADR) | WD_ENABLED, WD_ENABLE_IOADR); |
68 | return 0; |
69 | } |
70 | |
71 | static int wd_stop(struct watchdog_device *wdd) |
72 | { |
73 | outb(inb(WD_ENABLE_IOADR) & ~WD_ENABLED, WD_ENABLE_IOADR); |
74 | return 0; |
75 | } |
76 | |
77 | static int wd_ping(struct watchdog_device *wdd) |
78 | { |
79 | inb(WD_TRIGGER_IOADR); |
80 | return 0; |
81 | } |
82 | |
83 | static int wd_set_timeout(struct watchdog_device *wdd, unsigned int t) |
84 | { |
85 | int timeout_idx = find_closest(t, wd_timeout_table, |
86 | ARRAY_SIZE(wd_timeout_table)); |
87 | |
88 | outb(value: (inb(WD_ENABLE_IOADR) & 0xc7) | timeout_idx << 3, WD_ENABLE_IOADR); |
89 | wdd->timeout = wd_timeout_table[timeout_idx]; |
90 | return 0; |
91 | } |
92 | |
93 | static const struct watchdog_info wdt_ident = { |
94 | .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | |
95 | WDIOF_SETTIMEOUT, |
96 | .identity = KBUILD_MODNAME, |
97 | }; |
98 | |
99 | static const struct watchdog_ops wdt_ops = { |
100 | .owner = THIS_MODULE, |
101 | .start = wd_start, |
102 | .stop = wd_stop, |
103 | .ping = wd_ping, |
104 | .set_timeout = wd_set_timeout, |
105 | }; |
106 | |
107 | static void wd_secondary_enable(u32 wdtmode) |
108 | { |
109 | u16 resetbit; |
110 | |
111 | /* set safe_en_n so we are not just WDIOF_ALARMONLY */ |
112 | if (wdtmode == SIMATIC_IPC_DEVICE_227E) { |
113 | /* enable SAFE_EN_N on GP_STATUS_REG_227E */ |
114 | resetbit = inb(GP_STATUS_REG_227E); |
115 | outb(value: resetbit & ~SAFE_EN_N_227E, GP_STATUS_REG_227E); |
116 | } else { |
117 | /* enable SAFE_EN_N on PCH D1600 */ |
118 | resetbit = ioread16(wd_reset_base_addr); |
119 | iowrite16(resetbit & ~SAFE_EN_N_427E, wd_reset_base_addr); |
120 | } |
121 | } |
122 | |
123 | static int wd_setup(u32 wdtmode) |
124 | { |
125 | unsigned int bootstatus = 0; |
126 | int timeout_idx; |
127 | |
128 | timeout_idx = find_closest(TIMEOUT_DEF, wd_timeout_table, |
129 | ARRAY_SIZE(wd_timeout_table)); |
130 | |
131 | if (inb(WD_ENABLE_IOADR) & WD_TRIGGERED) |
132 | bootstatus |= WDIOF_CARDRESET; |
133 | |
134 | /* reset alarm bit, set macro mode, and set timeout */ |
135 | outb(WD_TRIGGERED | WD_MACROMODE | timeout_idx << 3, WD_ENABLE_IOADR); |
136 | |
137 | wd_secondary_enable(wdtmode); |
138 | |
139 | return bootstatus; |
140 | } |
141 | |
142 | static struct watchdog_device wdd_data = { |
143 | .info = &wdt_ident, |
144 | .ops = &wdt_ops, |
145 | .min_timeout = TIMEOUT_MIN, |
146 | .max_timeout = TIMEOUT_MAX |
147 | }; |
148 | |
149 | static int simatic_ipc_wdt_probe(struct platform_device *pdev) |
150 | { |
151 | struct simatic_ipc_platform *plat = pdev->dev.platform_data; |
152 | struct device *dev = &pdev->dev; |
153 | struct resource *res; |
154 | int ret; |
155 | |
156 | switch (plat->devmode) { |
157 | case SIMATIC_IPC_DEVICE_227E: |
158 | res = &gp_status_reg_227e_res; |
159 | if (!request_muxed_region(res->start, resource_size(res), res->name)) { |
160 | dev_err(dev, |
161 | "Unable to register IO resource at %pR\n" , |
162 | &gp_status_reg_227e_res); |
163 | return -EBUSY; |
164 | } |
165 | fallthrough; |
166 | case SIMATIC_IPC_DEVICE_427E: |
167 | wdd_data.parent = dev; |
168 | break; |
169 | default: |
170 | return -EINVAL; |
171 | } |
172 | |
173 | if (!devm_request_region(dev, io_resource_enable.start, |
174 | resource_size(&io_resource_enable), |
175 | io_resource_enable.name)) { |
176 | dev_err(dev, |
177 | "Unable to register IO resource at %#x\n" , |
178 | WD_ENABLE_IOADR); |
179 | return -EBUSY; |
180 | } |
181 | |
182 | if (!devm_request_region(dev, io_resource_trigger.start, |
183 | resource_size(&io_resource_trigger), |
184 | io_resource_trigger.name)) { |
185 | dev_err(dev, |
186 | "Unable to register IO resource at %#x\n" , |
187 | WD_TRIGGER_IOADR); |
188 | return -EBUSY; |
189 | } |
190 | |
191 | if (plat->devmode == SIMATIC_IPC_DEVICE_427E) { |
192 | res = &mem_resource; |
193 | |
194 | ret = p2sb_bar(NULL, devfn: 0, mem: res); |
195 | if (ret) |
196 | return ret; |
197 | |
198 | /* do the final address calculation */ |
199 | res->start = res->start + (GPIO_COMMUNITY0_PORT_ID << 16) + |
200 | PAD_CFG_DW0_GPP_A_23; |
201 | res->end = res->start + SZ_4 - 1; |
202 | |
203 | wd_reset_base_addr = devm_ioremap_resource(dev, res); |
204 | if (IS_ERR(ptr: wd_reset_base_addr)) |
205 | return PTR_ERR(ptr: wd_reset_base_addr); |
206 | } |
207 | |
208 | wdd_data.bootstatus = wd_setup(wdtmode: plat->devmode); |
209 | if (wdd_data.bootstatus) |
210 | dev_warn(dev, "last reboot caused by watchdog reset\n" ); |
211 | |
212 | if (plat->devmode == SIMATIC_IPC_DEVICE_227E) |
213 | release_region(gp_status_reg_227e_res.start, |
214 | resource_size(&gp_status_reg_227e_res)); |
215 | |
216 | watchdog_set_nowayout(wdd: &wdd_data, nowayout); |
217 | watchdog_stop_on_reboot(wdd: &wdd_data); |
218 | return devm_watchdog_register_device(dev, &wdd_data); |
219 | } |
220 | |
221 | static struct platform_driver simatic_ipc_wdt_driver = { |
222 | .probe = simatic_ipc_wdt_probe, |
223 | .driver = { |
224 | .name = KBUILD_MODNAME, |
225 | }, |
226 | }; |
227 | |
228 | module_platform_driver(simatic_ipc_wdt_driver); |
229 | |
230 | MODULE_LICENSE("GPL v2" ); |
231 | MODULE_ALIAS("platform:" KBUILD_MODNAME); |
232 | MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>" ); |
233 | |