1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Congatec Board Controller watchdog driver 4 * 5 * Copyright (C) 2024 Bootlin 6 * Author: Thomas Richard <thomas.richard@bootlin.com> 7 */ 8 9 #include <linux/build_bug.h> 10 #include <linux/device.h> 11 #include <linux/limits.h> 12 #include <linux/module.h> 13 #include <linux/platform_device.h> 14 #include <linux/watchdog.h> 15 16 #include <linux/mfd/cgbc.h> 17 18 #define CGBC_WDT_CMD_TRIGGER 0x27 19 #define CGBC_WDT_CMD_INIT 0x28 20 #define CGBC_WDT_DISABLE 0x00 21 22 #define CGBC_WDT_MODE_SINGLE_EVENT 0x02 23 24 #define CGBC_WDT_MIN_TIMEOUT 1 25 #define CGBC_WDT_MAX_TIMEOUT ((U32_MAX >> 8) / 1000) 26 27 #define CGBC_WDT_DEFAULT_TIMEOUT 30 28 #define CGBC_WDT_DEFAULT_PRETIMEOUT 0 29 30 enum action { 31 ACTION_INT = 0, 32 ACTION_SMI, 33 ACTION_RESET, 34 ACTION_BUTTON, 35 }; 36 37 static unsigned int timeout; 38 module_param(timeout, uint, 0); 39 MODULE_PARM_DESC(timeout, 40 "Watchdog timeout in seconds. (>=0, default=" 41 __MODULE_STRING(CGBC_WDT_DEFAULT_TIMEOUT) ")"); 42 43 static unsigned int pretimeout = CGBC_WDT_DEFAULT_PRETIMEOUT; 44 module_param(pretimeout, uint, 0); 45 MODULE_PARM_DESC(pretimeout, 46 "Watchdog pretimeout in seconds. (>=0, default=" 47 __MODULE_STRING(CGBC_WDT_DEFAULT_PRETIMEOUT) ")"); 48 49 static bool nowayout = WATCHDOG_NOWAYOUT; 50 module_param(nowayout, bool, 0); 51 MODULE_PARM_DESC(nowayout, 52 "Watchdog cannot be stopped once started (default=" 53 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 54 55 struct cgbc_wdt_data { 56 struct cgbc_device_data *cgbc; 57 struct watchdog_device wdd; 58 }; 59 60 struct cgbc_wdt_cmd_cfg { 61 u8 cmd; 62 u8 mode; 63 u8 action; 64 u8 timeout1[3]; 65 u8 timeout2[3]; 66 u8 reserved[3]; 67 u8 delay[3]; 68 } __packed; 69 70 static_assert(sizeof(struct cgbc_wdt_cmd_cfg) == 15); 71 72 static int cgbc_wdt_start(struct watchdog_device *wdd) 73 { 74 struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 75 struct cgbc_device_data *cgbc = wdt_data->cgbc; 76 unsigned int timeout1 = (wdd->timeout - wdd->pretimeout) * 1000; 77 unsigned int timeout2 = wdd->pretimeout * 1000; 78 u8 action; 79 80 struct cgbc_wdt_cmd_cfg cmd_start = { 81 .cmd = CGBC_WDT_CMD_INIT, 82 .mode = CGBC_WDT_MODE_SINGLE_EVENT, 83 .timeout1[0] = (u8)timeout1, 84 .timeout1[1] = (u8)(timeout1 >> 8), 85 .timeout1[2] = (u8)(timeout1 >> 16), 86 .timeout2[0] = (u8)timeout2, 87 .timeout2[1] = (u8)(timeout2 >> 8), 88 .timeout2[2] = (u8)(timeout2 >> 16), 89 }; 90 91 if (wdd->pretimeout) { 92 action = 2; 93 action |= ACTION_SMI << 2; 94 action |= ACTION_RESET << 4; 95 } else { 96 action = 1; 97 action |= ACTION_RESET << 2; 98 } 99 100 cmd_start.action = action; 101 102 return cgbc_command(cgbc, &cmd_start, sizeof(cmd_start), NULL, 0, NULL); 103 } 104 105 static int cgbc_wdt_stop(struct watchdog_device *wdd) 106 { 107 struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 108 struct cgbc_device_data *cgbc = wdt_data->cgbc; 109 struct cgbc_wdt_cmd_cfg cmd_stop = { 110 .cmd = CGBC_WDT_CMD_INIT, 111 .mode = CGBC_WDT_DISABLE, 112 }; 113 114 return cgbc_command(cgbc, &cmd_stop, sizeof(cmd_stop), NULL, 0, NULL); 115 } 116 117 static int cgbc_wdt_keepalive(struct watchdog_device *wdd) 118 { 119 struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 120 struct cgbc_device_data *cgbc = wdt_data->cgbc; 121 u8 cmd_ping = CGBC_WDT_CMD_TRIGGER; 122 123 return cgbc_command(cgbc, &cmd_ping, sizeof(cmd_ping), NULL, 0, NULL); 124 } 125 126 static int cgbc_wdt_set_pretimeout(struct watchdog_device *wdd, 127 unsigned int pretimeout) 128 { 129 wdd->pretimeout = pretimeout; 130 131 if (watchdog_active(wdd)) 132 return cgbc_wdt_start(wdd); 133 134 return 0; 135 } 136 137 static int cgbc_wdt_set_timeout(struct watchdog_device *wdd, 138 unsigned int timeout) 139 { 140 if (timeout < wdd->pretimeout) 141 wdd->pretimeout = 0; 142 143 wdd->timeout = timeout; 144 145 if (watchdog_active(wdd)) 146 return cgbc_wdt_start(wdd); 147 148 return 0; 149 } 150 151 static const struct watchdog_info cgbc_wdt_info = { 152 .identity = "CGBC Watchdog", 153 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 154 WDIOF_MAGICCLOSE | WDIOF_PRETIMEOUT 155 }; 156 157 static const struct watchdog_ops cgbc_wdt_ops = { 158 .owner = THIS_MODULE, 159 .start = cgbc_wdt_start, 160 .stop = cgbc_wdt_stop, 161 .ping = cgbc_wdt_keepalive, 162 .set_timeout = cgbc_wdt_set_timeout, 163 .set_pretimeout = cgbc_wdt_set_pretimeout, 164 }; 165 166 static int cgbc_wdt_probe(struct platform_device *pdev) 167 { 168 struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent); 169 struct device *dev = &pdev->dev; 170 struct cgbc_wdt_data *wdt_data; 171 struct watchdog_device *wdd; 172 173 wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL); 174 if (!wdt_data) 175 return -ENOMEM; 176 177 wdt_data->cgbc = cgbc; 178 wdd = &wdt_data->wdd; 179 wdd->parent = dev; 180 181 wdd->info = &cgbc_wdt_info; 182 wdd->ops = &cgbc_wdt_ops; 183 wdd->max_timeout = CGBC_WDT_MAX_TIMEOUT; 184 wdd->min_timeout = CGBC_WDT_MIN_TIMEOUT; 185 186 watchdog_set_drvdata(wdd, wdt_data); 187 watchdog_set_nowayout(wdd, nowayout); 188 189 wdd->timeout = CGBC_WDT_DEFAULT_TIMEOUT; 190 watchdog_init_timeout(wdd, timeout, dev); 191 cgbc_wdt_set_pretimeout(wdd, pretimeout); 192 193 platform_set_drvdata(pdev, wdt_data); 194 watchdog_stop_on_reboot(wdd); 195 watchdog_stop_on_unregister(wdd); 196 197 return devm_watchdog_register_device(dev, wdd); 198 } 199 200 static struct platform_driver cgbc_wdt_driver = { 201 .driver = { 202 .name = "cgbc-wdt", 203 }, 204 .probe = cgbc_wdt_probe, 205 }; 206 207 module_platform_driver(cgbc_wdt_driver); 208 209 MODULE_DESCRIPTION("Congatec Board Controller Watchdog Driver"); 210 MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>"); 211 MODULE_LICENSE("GPL"); 212