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
wdt_command(struct mid_wdt * mid,int sub,const void * in,size_t inlen,size_t size)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
wdt_start(struct watchdog_device * wd)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
wdt_ping(struct watchdog_device * wd)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
wdt_stop(struct watchdog_device * wd)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
mid_wdt_irq(int irq,void * dev_id)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
mid_wdt_probe(struct platform_device * pdev)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
157f285c953SAndy Shevchenko mid->scu = devm_intel_scu_ipc_dev_get(dev);
158f285c953SAndy Shevchenko if (!mid->scu)
159f285c953SAndy Shevchenko return -EPROBE_DEFER;
160f285c953SAndy 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*cf38e769SRaag Jadav MODULE_ALIAS("platform:intel_mid_wdt");
207