xref: /openbmc/linux/drivers/watchdog/intel-mid_wdt.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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