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