338 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| 
 | |
| /*
 | |
|  * Driver for watchdog aspect of for Zodiac Inflight Innovations RAVE
 | |
|  * Supervisory Processor(SP) MCU
 | |
|  *
 | |
|  * Copyright (C) 2017 Zodiac Inflight Innovation
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include <linux/delay.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/mfd/rave-sp.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/nvmem-consumer.h>
 | |
| #include <linux/of_device.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/reboot.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/watchdog.h>
 | |
| 
 | |
| enum {
 | |
| 	RAVE_SP_RESET_BYTE = 1,
 | |
| 	RAVE_SP_RESET_REASON_NORMAL = 0,
 | |
| 	RAVE_SP_RESET_DELAY_MS = 500,
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * struct rave_sp_wdt_variant - RAVE SP watchdog variant
 | |
|  *
 | |
|  * @max_timeout:	Largest possible watchdog timeout setting
 | |
|  * @min_timeout:	Smallest possible watchdog timeout setting
 | |
|  *
 | |
|  * @configure:		Function to send configuration command
 | |
|  * @restart:		Function to send "restart" command
 | |
|  */
 | |
| struct rave_sp_wdt_variant {
 | |
| 	unsigned int max_timeout;
 | |
| 	unsigned int min_timeout;
 | |
| 
 | |
| 	int (*configure)(struct watchdog_device *, bool);
 | |
| 	int (*restart)(struct watchdog_device *);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * struct rave_sp_wdt - RAVE SP watchdog
 | |
|  *
 | |
|  * @wdd:		Underlying watchdog device
 | |
|  * @sp:			Pointer to parent RAVE SP device
 | |
|  * @variant:		Device specific variant information
 | |
|  * @reboot_notifier:	Reboot notifier implementing machine reset
 | |
|  */
 | |
| struct rave_sp_wdt {
 | |
| 	struct watchdog_device wdd;
 | |
| 	struct rave_sp *sp;
 | |
| 	const struct rave_sp_wdt_variant *variant;
 | |
| 	struct notifier_block reboot_notifier;
 | |
| };
 | |
| 
 | |
| static struct rave_sp_wdt *to_rave_sp_wdt(struct watchdog_device *wdd)
 | |
| {
 | |
| 	return container_of(wdd, struct rave_sp_wdt, wdd);
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_exec(struct watchdog_device *wdd, void *data,
 | |
| 			    size_t data_size)
 | |
| {
 | |
| 	return rave_sp_exec(to_rave_sp_wdt(wdd)->sp,
 | |
| 			    data, data_size, NULL, 0);
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_legacy_configure(struct watchdog_device *wdd, bool on)
 | |
| {
 | |
| 	u8 cmd[] = {
 | |
| 		[0] = RAVE_SP_CMD_SW_WDT,
 | |
| 		[1] = 0,
 | |
| 		[2] = 0,
 | |
| 		[3] = on,
 | |
| 		[4] = on ? wdd->timeout : 0,
 | |
| 	};
 | |
| 
 | |
| 	return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_rdu_configure(struct watchdog_device *wdd, bool on)
 | |
| {
 | |
| 	u8 cmd[] = {
 | |
| 		[0] = RAVE_SP_CMD_SW_WDT,
 | |
| 		[1] = 0,
 | |
| 		[2] = on,
 | |
| 		[3] = (u8)wdd->timeout,
 | |
| 		[4] = (u8)(wdd->timeout >> 8),
 | |
| 	};
 | |
| 
 | |
| 	return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * rave_sp_wdt_configure - Configure watchdog device
 | |
|  *
 | |
|  * @wdd:	Device to configure
 | |
|  * @on:		Desired state of the watchdog timer (ON/OFF)
 | |
|  *
 | |
|  * This function configures two aspects of the watchdog timer:
 | |
|  *
 | |
|  *  - Wheither it is ON or OFF
 | |
|  *  - Its timeout duration
 | |
|  *
 | |
|  * with first aspect specified via function argument and second via
 | |
|  * the value of 'wdd->timeout'.
 | |
|  */
 | |
| static int rave_sp_wdt_configure(struct watchdog_device *wdd, bool on)
 | |
| {
 | |
| 	return to_rave_sp_wdt(wdd)->variant->configure(wdd, on);
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_legacy_restart(struct watchdog_device *wdd)
 | |
| {
 | |
| 	u8 cmd[] = {
 | |
| 		[0] = RAVE_SP_CMD_RESET,
 | |
| 		[1] = 0,
 | |
| 		[2] = RAVE_SP_RESET_BYTE
 | |
| 	};
 | |
| 
 | |
| 	return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_rdu_restart(struct watchdog_device *wdd)
 | |
| {
 | |
| 	u8 cmd[] = {
 | |
| 		[0] = RAVE_SP_CMD_RESET,
 | |
| 		[1] = 0,
 | |
| 		[2] = RAVE_SP_RESET_BYTE,
 | |
| 		[3] = RAVE_SP_RESET_REASON_NORMAL
 | |
| 	};
 | |
| 
 | |
| 	return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_reboot_notifier(struct notifier_block *nb,
 | |
| 				       unsigned long action, void *data)
 | |
| {
 | |
| 	/*
 | |
| 	 * Restart handler is called in atomic context which means we
 | |
| 	 * can't communicate to SP via UART. Luckily for use SP will
 | |
| 	 * wait 500ms before actually resetting us, so we ask it to do
 | |
| 	 * so here and let the rest of the system go on wrapping
 | |
| 	 * things up.
 | |
| 	 */
 | |
| 	if (action == SYS_DOWN || action == SYS_HALT) {
 | |
| 		struct rave_sp_wdt *sp_wd =
 | |
| 			container_of(nb, struct rave_sp_wdt, reboot_notifier);
 | |
| 
 | |
| 		const int ret = sp_wd->variant->restart(&sp_wd->wdd);
 | |
| 
 | |
| 		if (ret < 0)
 | |
| 			dev_err(sp_wd->wdd.parent,
 | |
| 				"Failed to issue restart command (%d)", ret);
 | |
| 		return NOTIFY_OK;
 | |
| 	}
 | |
| 
 | |
| 	return NOTIFY_DONE;
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_restart(struct watchdog_device *wdd,
 | |
| 			       unsigned long action, void *data)
 | |
| {
 | |
| 	/*
 | |
| 	 * The actual work was done by reboot notifier above. SP
 | |
| 	 * firmware waits 500 ms before issuing reset, so let's hang
 | |
| 	 * here for twice that delay and hopefuly we'd never reach
 | |
| 	 * the return statement.
 | |
| 	 */
 | |
| 	mdelay(2 * RAVE_SP_RESET_DELAY_MS);
 | |
| 
 | |
| 	return -EIO;
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_start(struct watchdog_device *wdd)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = rave_sp_wdt_configure(wdd, true);
 | |
| 	if (!ret)
 | |
| 		set_bit(WDOG_HW_RUNNING, &wdd->status);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_stop(struct watchdog_device *wdd)
 | |
| {
 | |
| 	return rave_sp_wdt_configure(wdd, false);
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_set_timeout(struct watchdog_device *wdd,
 | |
| 				   unsigned int timeout)
 | |
| {
 | |
| 	wdd->timeout = timeout;
 | |
| 
 | |
| 	return rave_sp_wdt_configure(wdd, watchdog_active(wdd));
 | |
| }
 | |
| 
 | |
| static int rave_sp_wdt_ping(struct watchdog_device *wdd)
 | |
| {
 | |
| 	u8 cmd[] = {
 | |
| 		[0] = RAVE_SP_CMD_PET_WDT,
 | |
| 		[1] = 0,
 | |
| 	};
 | |
| 
 | |
| 	return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
 | |
| }
 | |
| 
 | |
| static const struct watchdog_info rave_sp_wdt_info = {
 | |
| 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 | |
| 	.identity = "RAVE SP Watchdog",
 | |
| };
 | |
| 
 | |
| static const struct watchdog_ops rave_sp_wdt_ops = {
 | |
| 	.owner = THIS_MODULE,
 | |
| 	.start = rave_sp_wdt_start,
 | |
| 	.stop = rave_sp_wdt_stop,
 | |
| 	.ping = rave_sp_wdt_ping,
 | |
| 	.set_timeout = rave_sp_wdt_set_timeout,
 | |
| 	.restart = rave_sp_wdt_restart,
 | |
| };
 | |
| 
 | |
| static const struct rave_sp_wdt_variant rave_sp_wdt_legacy = {
 | |
| 	.max_timeout = 255,
 | |
| 	.min_timeout = 1,
 | |
| 	.configure = rave_sp_wdt_legacy_configure,
 | |
| 	.restart   = rave_sp_wdt_legacy_restart,
 | |
| };
 | |
| 
 | |
| static const struct rave_sp_wdt_variant rave_sp_wdt_rdu = {
 | |
| 	.max_timeout = 180,
 | |
| 	.min_timeout = 60,
 | |
| 	.configure = rave_sp_wdt_rdu_configure,
 | |
| 	.restart   = rave_sp_wdt_rdu_restart,
 | |
| };
 | |
| 
 | |
| static const struct of_device_id rave_sp_wdt_of_match[] = {
 | |
| 	{
 | |
| 		.compatible = "zii,rave-sp-watchdog-legacy",
 | |
| 		.data = &rave_sp_wdt_legacy,
 | |
| 	},
 | |
| 	{
 | |
| 		.compatible = "zii,rave-sp-watchdog",
 | |
| 		.data = &rave_sp_wdt_rdu,
 | |
| 	},
 | |
| 	{ /* sentinel */ }
 | |
| };
 | |
| 
 | |
| static int rave_sp_wdt_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct device *dev = &pdev->dev;
 | |
| 	struct watchdog_device *wdd;
 | |
| 	struct rave_sp_wdt *sp_wd;
 | |
| 	struct nvmem_cell *cell;
 | |
| 	__le16 timeout = 0;
 | |
| 	int ret;
 | |
| 
 | |
| 	sp_wd = devm_kzalloc(dev, sizeof(*sp_wd), GFP_KERNEL);
 | |
| 	if (!sp_wd)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	sp_wd->variant = of_device_get_match_data(dev);
 | |
| 	sp_wd->sp      = dev_get_drvdata(dev->parent);
 | |
| 
 | |
| 	wdd              = &sp_wd->wdd;
 | |
| 	wdd->parent      = dev;
 | |
| 	wdd->info        = &rave_sp_wdt_info;
 | |
| 	wdd->ops         = &rave_sp_wdt_ops;
 | |
| 	wdd->min_timeout = sp_wd->variant->min_timeout;
 | |
| 	wdd->max_timeout = sp_wd->variant->max_timeout;
 | |
| 	wdd->status      = WATCHDOG_NOWAYOUT_INIT_STATUS;
 | |
| 	wdd->timeout     = 60;
 | |
| 
 | |
| 	cell = nvmem_cell_get(dev, "wdt-timeout");
 | |
| 	if (!IS_ERR(cell)) {
 | |
| 		size_t len;
 | |
| 		void *value = nvmem_cell_read(cell, &len);
 | |
| 
 | |
| 		if (!IS_ERR(value)) {
 | |
| 			memcpy(&timeout, value, min(len, sizeof(timeout)));
 | |
| 			kfree(value);
 | |
| 		}
 | |
| 		nvmem_cell_put(cell);
 | |
| 	}
 | |
| 	watchdog_init_timeout(wdd, le16_to_cpu(timeout), dev);
 | |
| 	watchdog_set_restart_priority(wdd, 255);
 | |
| 	watchdog_stop_on_unregister(wdd);
 | |
| 
 | |
| 	sp_wd->reboot_notifier.notifier_call = rave_sp_wdt_reboot_notifier;
 | |
| 	ret = devm_register_reboot_notifier(dev, &sp_wd->reboot_notifier);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to register reboot notifier\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * We don't know if watchdog is running now. To be sure, let's
 | |
| 	 * start it and depend on watchdog core to ping it
 | |
| 	 */
 | |
| 	wdd->max_hw_heartbeat_ms = wdd->max_timeout * 1000;
 | |
| 	ret = rave_sp_wdt_start(wdd);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Watchdog didn't start\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = devm_watchdog_register_device(dev, wdd);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to register watchdog device\n");
 | |
| 		rave_sp_wdt_stop(wdd);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct platform_driver rave_sp_wdt_driver = {
 | |
| 	.probe = rave_sp_wdt_probe,
 | |
| 	.driver = {
 | |
| 		.name = KBUILD_MODNAME,
 | |
| 		.of_match_table = rave_sp_wdt_of_match,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| module_platform_driver(rave_sp_wdt_driver);
 | |
| 
 | |
| MODULE_DEVICE_TABLE(of, rave_sp_wdt_of_match);
 | |
| MODULE_LICENSE("GPL");
 | |
| MODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>");
 | |
| MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>");
 | |
| MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
 | |
| MODULE_DESCRIPTION("RAVE SP Watchdog driver");
 | |
| MODULE_ALIAS("platform:rave-sp-watchdog");
 | 
