125763b3cSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 287a1ef80SDavid Cohen /* 387a1ef80SDavid Cohen * intel-mid_wdt: generic Intel MID SCU watchdog driver 487a1ef80SDavid Cohen * 587a1ef80SDavid Cohen * Platforms supported so far: 687a1ef80SDavid Cohen * - Merrifield only 787a1ef80SDavid Cohen * 887a1ef80SDavid Cohen * Copyright (C) 2014 Intel Corporation. All rights reserved. 987a1ef80SDavid Cohen * Contact: David Cohen <david.a.cohen@linux.intel.com> 1087a1ef80SDavid Cohen */ 1187a1ef80SDavid Cohen 1287a1ef80SDavid Cohen #include <linux/interrupt.h> 1387a1ef80SDavid Cohen #include <linux/module.h> 1487a1ef80SDavid Cohen #include <linux/nmi.h> 1587a1ef80SDavid Cohen #include <linux/platform_device.h> 1687a1ef80SDavid Cohen #include <linux/watchdog.h> 1787a1ef80SDavid Cohen #include <linux/platform_data/intel-mid_wdt.h> 1887a1ef80SDavid Cohen 1987a1ef80SDavid Cohen #include <asm/intel_scu_ipc.h> 2087a1ef80SDavid Cohen #include <asm/intel-mid.h> 2187a1ef80SDavid Cohen 2287a1ef80SDavid Cohen #define IPC_WATCHDOG 0xf8 2387a1ef80SDavid Cohen 2487a1ef80SDavid Cohen #define MID_WDT_PRETIMEOUT 15 2587a1ef80SDavid Cohen #define MID_WDT_TIMEOUT_MIN (1 + MID_WDT_PRETIMEOUT) 2687a1ef80SDavid Cohen #define MID_WDT_TIMEOUT_MAX 170 2787a1ef80SDavid Cohen #define MID_WDT_DEFAULT_TIMEOUT 90 2887a1ef80SDavid Cohen 2987a1ef80SDavid Cohen /* SCU watchdog messages */ 3087a1ef80SDavid Cohen enum { 3187a1ef80SDavid Cohen SCU_WATCHDOG_START = 0, 3287a1ef80SDavid Cohen SCU_WATCHDOG_STOP, 3387a1ef80SDavid Cohen SCU_WATCHDOG_KEEPALIVE, 3487a1ef80SDavid Cohen }; 3587a1ef80SDavid Cohen 3680ae679bSMika Westerberg struct mid_wdt { 3780ae679bSMika Westerberg struct watchdog_device wd; 3880ae679bSMika Westerberg struct device *dev; 3980ae679bSMika Westerberg struct intel_scu_ipc_dev *scu; 4080ae679bSMika Westerberg }; 4180ae679bSMika Westerberg 4280ae679bSMika Westerberg static inline int 4380ae679bSMika Westerberg wdt_command(struct mid_wdt *mid, int sub, const void *in, size_t inlen, size_t size) 4487a1ef80SDavid Cohen { 4580ae679bSMika Westerberg struct intel_scu_ipc_dev *scu = mid->scu; 4680ae679bSMika Westerberg 4780ae679bSMika Westerberg return intel_scu_ipc_dev_command_with_size(scu, IPC_WATCHDOG, sub, in, 4880ae679bSMika Westerberg inlen, size, NULL, 0); 4987a1ef80SDavid Cohen } 5087a1ef80SDavid Cohen 5187a1ef80SDavid Cohen static int wdt_start(struct watchdog_device *wd) 5287a1ef80SDavid Cohen { 5380ae679bSMika Westerberg struct mid_wdt *mid = watchdog_get_drvdata(wd); 5487a1ef80SDavid Cohen int ret, in_size; 5587a1ef80SDavid Cohen int timeout = wd->timeout; 5687a1ef80SDavid Cohen struct ipc_wd_start { 5787a1ef80SDavid Cohen u32 pretimeout; 5887a1ef80SDavid Cohen u32 timeout; 5987a1ef80SDavid Cohen } ipc_wd_start = { timeout - MID_WDT_PRETIMEOUT, timeout }; 6087a1ef80SDavid Cohen 6187a1ef80SDavid Cohen /* 6280ae679bSMika Westerberg * SCU expects the input size for watchdog IPC to be 2 which is the 6380ae679bSMika Westerberg * size of the structure in dwords. SCU IPC normally takes bytes 6480ae679bSMika Westerberg * but this is a special case where we specify size to be different 6580ae679bSMika Westerberg * than inlen. 6687a1ef80SDavid Cohen */ 6787a1ef80SDavid Cohen in_size = DIV_ROUND_UP(sizeof(ipc_wd_start), 4); 6887a1ef80SDavid Cohen 6980ae679bSMika Westerberg ret = wdt_command(mid, SCU_WATCHDOG_START, &ipc_wd_start, 7080ae679bSMika Westerberg sizeof(ipc_wd_start), in_size); 71bb790362SAndy Shevchenko if (ret) 7280ae679bSMika Westerberg dev_crit(mid->dev, "error starting watchdog: %d\n", ret); 7387a1ef80SDavid Cohen 7487a1ef80SDavid Cohen return ret; 7587a1ef80SDavid Cohen } 7687a1ef80SDavid Cohen 7787a1ef80SDavid Cohen static int wdt_ping(struct watchdog_device *wd) 7887a1ef80SDavid Cohen { 7980ae679bSMika Westerberg struct mid_wdt *mid = watchdog_get_drvdata(wd); 8087a1ef80SDavid Cohen int ret; 8187a1ef80SDavid Cohen 8280ae679bSMika Westerberg ret = wdt_command(mid, SCU_WATCHDOG_KEEPALIVE, NULL, 0, 0); 83bb790362SAndy Shevchenko if (ret) 8480ae679bSMika Westerberg dev_crit(mid->dev, "Error executing keepalive: %d\n", ret); 8587a1ef80SDavid Cohen 8687a1ef80SDavid Cohen return ret; 8787a1ef80SDavid Cohen } 8887a1ef80SDavid Cohen 8987a1ef80SDavid Cohen static int wdt_stop(struct watchdog_device *wd) 9087a1ef80SDavid Cohen { 9180ae679bSMika Westerberg struct mid_wdt *mid = watchdog_get_drvdata(wd); 9287a1ef80SDavid Cohen int ret; 9387a1ef80SDavid Cohen 9480ae679bSMika Westerberg ret = wdt_command(mid, SCU_WATCHDOG_STOP, NULL, 0, 0); 95bb790362SAndy Shevchenko if (ret) 9680ae679bSMika Westerberg dev_crit(mid->dev, "Error stopping watchdog: %d\n", ret); 9787a1ef80SDavid Cohen 9887a1ef80SDavid Cohen return ret; 9987a1ef80SDavid Cohen } 10087a1ef80SDavid Cohen 10187a1ef80SDavid Cohen static irqreturn_t mid_wdt_irq(int irq, void *dev_id) 10287a1ef80SDavid Cohen { 10387a1ef80SDavid Cohen panic("Kernel Watchdog"); 10487a1ef80SDavid Cohen 10587a1ef80SDavid Cohen /* This code should not be reached */ 10687a1ef80SDavid Cohen return IRQ_HANDLED; 10787a1ef80SDavid Cohen } 10887a1ef80SDavid Cohen 10987a1ef80SDavid Cohen static const struct watchdog_info mid_wdt_info = { 11087a1ef80SDavid Cohen .identity = "Intel MID SCU watchdog", 1118cbb97eaSDavid Cohen .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, 11287a1ef80SDavid Cohen }; 11387a1ef80SDavid Cohen 11487a1ef80SDavid Cohen static const struct watchdog_ops mid_wdt_ops = { 11587a1ef80SDavid Cohen .owner = THIS_MODULE, 11687a1ef80SDavid Cohen .start = wdt_start, 11787a1ef80SDavid Cohen .stop = wdt_stop, 11887a1ef80SDavid Cohen .ping = wdt_ping, 11987a1ef80SDavid Cohen }; 12087a1ef80SDavid Cohen 12187a1ef80SDavid Cohen static int mid_wdt_probe(struct platform_device *pdev) 12287a1ef80SDavid Cohen { 123b7b6adf3SGuenter Roeck struct device *dev = &pdev->dev; 12487a1ef80SDavid Cohen struct watchdog_device *wdt_dev; 125b7b6adf3SGuenter Roeck struct intel_mid_wdt_pdata *pdata = dev->platform_data; 12680ae679bSMika Westerberg struct mid_wdt *mid; 12787a1ef80SDavid Cohen int ret; 12887a1ef80SDavid Cohen 12987a1ef80SDavid Cohen if (!pdata) { 130b7b6adf3SGuenter Roeck dev_err(dev, "missing platform data\n"); 13187a1ef80SDavid Cohen return -EINVAL; 13287a1ef80SDavid Cohen } 13387a1ef80SDavid Cohen 13487a1ef80SDavid Cohen if (pdata->probe) { 13587a1ef80SDavid Cohen ret = pdata->probe(pdev); 13687a1ef80SDavid Cohen if (ret) 13787a1ef80SDavid Cohen return ret; 13887a1ef80SDavid Cohen } 13987a1ef80SDavid Cohen 14080ae679bSMika Westerberg mid = devm_kzalloc(dev, sizeof(*mid), GFP_KERNEL); 14180ae679bSMika Westerberg if (!mid) 14287a1ef80SDavid Cohen return -ENOMEM; 14387a1ef80SDavid Cohen 14480ae679bSMika Westerberg mid->dev = dev; 14580ae679bSMika Westerberg wdt_dev = &mid->wd; 14680ae679bSMika Westerberg 14787a1ef80SDavid Cohen wdt_dev->info = &mid_wdt_info; 14887a1ef80SDavid Cohen wdt_dev->ops = &mid_wdt_ops; 14987a1ef80SDavid Cohen wdt_dev->min_timeout = MID_WDT_TIMEOUT_MIN; 15087a1ef80SDavid Cohen wdt_dev->max_timeout = MID_WDT_TIMEOUT_MAX; 15187a1ef80SDavid Cohen wdt_dev->timeout = MID_WDT_DEFAULT_TIMEOUT; 152b7b6adf3SGuenter Roeck wdt_dev->parent = dev; 15387a1ef80SDavid Cohen 154ff0aaacbSAndy Shevchenko watchdog_set_nowayout(wdt_dev, WATCHDOG_NOWAYOUT); 15580ae679bSMika Westerberg watchdog_set_drvdata(wdt_dev, mid); 15687a1ef80SDavid Cohen 157*f285c953SAndy Shevchenko mid->scu = devm_intel_scu_ipc_dev_get(dev); 158*f285c953SAndy Shevchenko if (!mid->scu) 159*f285c953SAndy Shevchenko return -EPROBE_DEFER; 160*f285c953SAndy Shevchenko 161b7b6adf3SGuenter Roeck ret = devm_request_irq(dev, pdata->irq, mid_wdt_irq, 16287a1ef80SDavid Cohen IRQF_SHARED | IRQF_NO_SUSPEND, "watchdog", 16387a1ef80SDavid Cohen wdt_dev); 16487a1ef80SDavid Cohen if (ret) { 165b7b6adf3SGuenter Roeck dev_err(dev, "error requesting warning irq %d\n", pdata->irq); 16687a1ef80SDavid Cohen return ret; 16787a1ef80SDavid Cohen } 16887a1ef80SDavid Cohen 169954351e8SAndy Shevchenko /* 170954351e8SAndy Shevchenko * The firmware followed by U-Boot leaves the watchdog running 171954351e8SAndy Shevchenko * with the default threshold which may vary. When we get here 172954351e8SAndy Shevchenko * we should make a decision to prevent any side effects before 173954351e8SAndy Shevchenko * user space daemon will take care of it. The best option, 174954351e8SAndy Shevchenko * taking into consideration that there is no way to read values 175954351e8SAndy Shevchenko * back from hardware, is to enforce watchdog being run with 176954351e8SAndy Shevchenko * deterministic values. 177954351e8SAndy Shevchenko */ 178954351e8SAndy Shevchenko ret = wdt_start(wdt_dev); 179954351e8SAndy Shevchenko if (ret) 180954351e8SAndy Shevchenko return ret; 181954351e8SAndy Shevchenko 182954351e8SAndy Shevchenko /* Make sure the watchdog is serviced */ 183954351e8SAndy Shevchenko set_bit(WDOG_HW_RUNNING, &wdt_dev->status); 18431ecad65SAndy Shevchenko 185b7b6adf3SGuenter Roeck ret = devm_watchdog_register_device(dev, wdt_dev); 186ca2d4490SWolfram Sang if (ret) 18787a1ef80SDavid Cohen return ret; 18887a1ef80SDavid Cohen 189b7b6adf3SGuenter Roeck dev_info(dev, "Intel MID watchdog device probed\n"); 19087a1ef80SDavid Cohen 19187a1ef80SDavid Cohen return 0; 19287a1ef80SDavid Cohen } 19387a1ef80SDavid Cohen 19487a1ef80SDavid Cohen static struct platform_driver mid_wdt_driver = { 19587a1ef80SDavid Cohen .probe = mid_wdt_probe, 19687a1ef80SDavid Cohen .driver = { 19787a1ef80SDavid Cohen .name = "intel_mid_wdt", 19887a1ef80SDavid Cohen }, 19987a1ef80SDavid Cohen }; 20087a1ef80SDavid Cohen 20187a1ef80SDavid Cohen module_platform_driver(mid_wdt_driver); 20287a1ef80SDavid Cohen 20387a1ef80SDavid Cohen MODULE_AUTHOR("David Cohen <david.a.cohen@linux.intel.com>"); 20487a1ef80SDavid Cohen MODULE_DESCRIPTION("Watchdog Driver for Intel MID platform"); 20587a1ef80SDavid Cohen MODULE_LICENSE("GPL"); 206