| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * coh901327_wdt.c | 
 |  * | 
 |  * Copyright (C) 2008-2009 ST-Ericsson AB | 
 |  * Watchdog driver for the ST-Ericsson AB COH 901 327 IP core | 
 |  * Author: Linus Walleij <linus.walleij@stericsson.com> | 
 |  */ | 
 | #include <linux/module.h> | 
 | #include <linux/mod_devicetable.h> | 
 | #include <linux/types.h> | 
 | #include <linux/watchdog.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/pm.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/io.h> | 
 | #include <linux/bitops.h> | 
 | #include <linux/clk.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/err.h> | 
 |  | 
 | #define DRV_NAME "WDOG COH 901 327" | 
 |  | 
 | /* | 
 |  * COH 901 327 register definitions | 
 |  */ | 
 |  | 
 | /* WDOG_FEED Register 32bit (-/W) */ | 
 | #define U300_WDOG_FR							0x00 | 
 | #define U300_WDOG_FR_FEED_RESTART_TIMER					0xFEEDU | 
 | /* WDOG_TIMEOUT Register 32bit (R/W) */ | 
 | #define U300_WDOG_TR							0x04 | 
 | #define U300_WDOG_TR_TIMEOUT_MASK					0x7FFFU | 
 | /* WDOG_DISABLE1 Register 32bit (-/W) */ | 
 | #define U300_WDOG_D1R							0x08 | 
 | #define U300_WDOG_D1R_DISABLE1_DISABLE_TIMER				0x2BADU | 
 | /* WDOG_DISABLE2 Register 32bit (R/W) */ | 
 | #define U300_WDOG_D2R							0x0C | 
 | #define U300_WDOG_D2R_DISABLE2_DISABLE_TIMER				0xCAFEU | 
 | #define U300_WDOG_D2R_DISABLE_STATUS_DISABLED				0xDABEU | 
 | #define U300_WDOG_D2R_DISABLE_STATUS_ENABLED				0x0000U | 
 | /* WDOG_STATUS Register 32bit (R/W) */ | 
 | #define U300_WDOG_SR							0x10 | 
 | #define U300_WDOG_SR_STATUS_TIMED_OUT					0xCFE8U | 
 | #define U300_WDOG_SR_STATUS_NORMAL					0x0000U | 
 | #define U300_WDOG_SR_RESET_STATUS_RESET					0xE8B4U | 
 | /* WDOG_COUNT Register 32bit (R/-) */ | 
 | #define U300_WDOG_CR							0x14 | 
 | #define U300_WDOG_CR_VALID_IND						0x8000U | 
 | #define U300_WDOG_CR_VALID_STABLE					0x0000U | 
 | #define U300_WDOG_CR_COUNT_VALUE_MASK					0x7FFFU | 
 | /* WDOG_JTAGOVR Register 32bit (R/W) */ | 
 | #define U300_WDOG_JOR							0x18 | 
 | #define U300_WDOG_JOR_JTAG_MODE_IND					0x0002U | 
 | #define U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE				0x0001U | 
 | /* WDOG_RESTART Register 32bit (-/W) */ | 
 | #define U300_WDOG_RR							0x1C | 
 | #define U300_WDOG_RR_RESTART_VALUE_RESUME				0xACEDU | 
 | /* WDOG_IRQ_EVENT Register 32bit (R/W) */ | 
 | #define U300_WDOG_IER							0x20 | 
 | #define U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND				0x0001U | 
 | #define U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE				0x0001U | 
 | /* WDOG_IRQ_MASK Register 32bit (R/W) */ | 
 | #define U300_WDOG_IMR							0x24 | 
 | #define U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE				0x0001U | 
 | /* WDOG_IRQ_FORCE Register 32bit (R/W) */ | 
 | #define U300_WDOG_IFR							0x28 | 
 | #define U300_WDOG_IFR_WILL_BARK_IRQ_FORCE_ENABLE			0x0001U | 
 |  | 
 | /* Default timeout in seconds = 1 minute */ | 
 | #define U300_WDOG_DEFAULT_TIMEOUT					60 | 
 |  | 
 | static unsigned int margin; | 
 | static int irq; | 
 | static void __iomem *virtbase; | 
 | static struct device *parent; | 
 |  | 
 | static struct clk *clk; | 
 |  | 
 | /* | 
 |  * Enabling and disabling functions. | 
 |  */ | 
 | static void coh901327_enable(u16 timeout) | 
 | { | 
 | 	u16 val; | 
 | 	unsigned long freq; | 
 | 	unsigned long delay_ns; | 
 |  | 
 | 	/* Restart timer if it is disabled */ | 
 | 	val = readw(virtbase + U300_WDOG_D2R); | 
 | 	if (val == U300_WDOG_D2R_DISABLE_STATUS_DISABLED) | 
 | 		writew(U300_WDOG_RR_RESTART_VALUE_RESUME, | 
 | 		       virtbase + U300_WDOG_RR); | 
 | 	/* Acknowledge any pending interrupt so it doesn't just fire off */ | 
 | 	writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, | 
 | 	       virtbase + U300_WDOG_IER); | 
 | 	/* | 
 | 	 * The interrupt is cleared in the 32 kHz clock domain. | 
 | 	 * Wait 3 32 kHz cycles for it to take effect | 
 | 	 */ | 
 | 	freq = clk_get_rate(clk); | 
 | 	delay_ns = DIV_ROUND_UP(1000000000, freq); /* Freq to ns and round up */ | 
 | 	delay_ns = 3 * delay_ns; /* Wait 3 cycles */ | 
 | 	ndelay(delay_ns); | 
 | 	/* Enable the watchdog interrupt */ | 
 | 	writew(U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE, virtbase + U300_WDOG_IMR); | 
 | 	/* Activate the watchdog timer */ | 
 | 	writew(timeout, virtbase + U300_WDOG_TR); | 
 | 	/* Start the watchdog timer */ | 
 | 	writew(U300_WDOG_FR_FEED_RESTART_TIMER, virtbase + U300_WDOG_FR); | 
 | 	/* | 
 | 	 * Extra read so that this change propagate in the watchdog. | 
 | 	 */ | 
 | 	(void) readw(virtbase + U300_WDOG_CR); | 
 | 	val = readw(virtbase + U300_WDOG_D2R); | 
 | 	if (val != U300_WDOG_D2R_DISABLE_STATUS_ENABLED) | 
 | 		dev_err(parent, | 
 | 			"%s(): watchdog not enabled! D2R value %04x\n", | 
 | 			__func__, val); | 
 | } | 
 |  | 
 | static void coh901327_disable(void) | 
 | { | 
 | 	u16 val; | 
 |  | 
 | 	/* Disable the watchdog interrupt if it is active */ | 
 | 	writew(0x0000U, virtbase + U300_WDOG_IMR); | 
 | 	/* If the watchdog is currently enabled, attempt to disable it */ | 
 | 	val = readw(virtbase + U300_WDOG_D2R); | 
 | 	if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) { | 
 | 		writew(U300_WDOG_D1R_DISABLE1_DISABLE_TIMER, | 
 | 		       virtbase + U300_WDOG_D1R); | 
 | 		writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, | 
 | 		       virtbase + U300_WDOG_D2R); | 
 | 		/* Write this twice (else problems occur) */ | 
 | 		writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, | 
 | 		       virtbase + U300_WDOG_D2R); | 
 | 	} | 
 | 	val = readw(virtbase + U300_WDOG_D2R); | 
 | 	if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) | 
 | 		dev_err(parent, | 
 | 			"%s(): watchdog not disabled! D2R value %04x\n", | 
 | 			__func__, val); | 
 | } | 
 |  | 
 | static int coh901327_start(struct watchdog_device *wdt_dev) | 
 | { | 
 | 	coh901327_enable(wdt_dev->timeout * 100); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int coh901327_stop(struct watchdog_device *wdt_dev) | 
 | { | 
 | 	coh901327_disable(); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int coh901327_ping(struct watchdog_device *wdd) | 
 | { | 
 | 	/* Feed the watchdog */ | 
 | 	writew(U300_WDOG_FR_FEED_RESTART_TIMER, | 
 | 	       virtbase + U300_WDOG_FR); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int coh901327_settimeout(struct watchdog_device *wdt_dev, | 
 | 				unsigned int time) | 
 | { | 
 | 	wdt_dev->timeout = time; | 
 | 	/* Set new timeout value */ | 
 | 	writew(time * 100, virtbase + U300_WDOG_TR); | 
 | 	/* Feed the dog */ | 
 | 	writew(U300_WDOG_FR_FEED_RESTART_TIMER, | 
 | 	       virtbase + U300_WDOG_FR); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static unsigned int coh901327_gettimeleft(struct watchdog_device *wdt_dev) | 
 | { | 
 | 	u16 val; | 
 |  | 
 | 	/* Read repeatedly until the value is stable! */ | 
 | 	val = readw(virtbase + U300_WDOG_CR); | 
 | 	while (val & U300_WDOG_CR_VALID_IND) | 
 | 		val = readw(virtbase + U300_WDOG_CR); | 
 | 	val &= U300_WDOG_CR_COUNT_VALUE_MASK; | 
 | 	if (val != 0) | 
 | 		val /= 100; | 
 |  | 
 | 	return val; | 
 | } | 
 |  | 
 | /* | 
 |  * This interrupt occurs 10 ms before the watchdog WILL bark. | 
 |  */ | 
 | static irqreturn_t coh901327_interrupt(int irq, void *data) | 
 | { | 
 | 	u16 val; | 
 |  | 
 | 	/* | 
 | 	 * Ack IRQ? If this occurs we're FUBAR anyway, so | 
 | 	 * just acknowledge, disable the interrupt and await the imminent end. | 
 | 	 * If you at some point need a host of callbacks to be called | 
 | 	 * when the system is about to watchdog-reset, add them here! | 
 | 	 * | 
 | 	 * NOTE: on future versions of this IP-block, it will be possible | 
 | 	 * to prevent a watchdog reset by feeding the watchdog at this | 
 | 	 * point. | 
 | 	 */ | 
 | 	val = readw(virtbase + U300_WDOG_IER); | 
 | 	if (val == U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND) | 
 | 		writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, | 
 | 		       virtbase + U300_WDOG_IER); | 
 | 	writew(0x0000U, virtbase + U300_WDOG_IMR); | 
 | 	dev_crit(parent, "watchdog is barking!\n"); | 
 | 	return IRQ_HANDLED; | 
 | } | 
 |  | 
 | static const struct watchdog_info coh901327_ident = { | 
 | 	.options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | 
 | 	.identity = DRV_NAME, | 
 | }; | 
 |  | 
 | static const struct watchdog_ops coh901327_ops = { | 
 | 	.owner = THIS_MODULE, | 
 | 	.start = coh901327_start, | 
 | 	.stop = coh901327_stop, | 
 | 	.ping = coh901327_ping, | 
 | 	.set_timeout = coh901327_settimeout, | 
 | 	.get_timeleft = coh901327_gettimeleft, | 
 | }; | 
 |  | 
 | static struct watchdog_device coh901327_wdt = { | 
 | 	.info = &coh901327_ident, | 
 | 	.ops = &coh901327_ops, | 
 | 	/* | 
 | 	 * Max timeout is 327 since the 10ms | 
 | 	 * timeout register is max | 
 | 	 * 0x7FFF = 327670ms ~= 327s. | 
 | 	 */ | 
 | 	.min_timeout = 1, | 
 | 	.max_timeout = 327, | 
 | 	.timeout = U300_WDOG_DEFAULT_TIMEOUT, | 
 | }; | 
 |  | 
 | static int __exit coh901327_remove(struct platform_device *pdev) | 
 | { | 
 | 	watchdog_unregister_device(&coh901327_wdt); | 
 | 	coh901327_disable(); | 
 | 	free_irq(irq, pdev); | 
 | 	clk_disable_unprepare(clk); | 
 | 	clk_put(clk); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int __init coh901327_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct device *dev = &pdev->dev; | 
 | 	int ret; | 
 | 	u16 val; | 
 | 	struct resource *res; | 
 |  | 
 | 	parent = dev; | 
 |  | 
 | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 	virtbase = devm_ioremap_resource(dev, res); | 
 | 	if (IS_ERR(virtbase)) | 
 | 		return PTR_ERR(virtbase); | 
 |  | 
 | 	clk = clk_get(dev, NULL); | 
 | 	if (IS_ERR(clk)) { | 
 | 		ret = PTR_ERR(clk); | 
 | 		dev_err(dev, "could not get clock\n"); | 
 | 		return ret; | 
 | 	} | 
 | 	ret = clk_prepare_enable(clk); | 
 | 	if (ret) { | 
 | 		dev_err(dev, "could not prepare and enable clock\n"); | 
 | 		goto out_no_clk_enable; | 
 | 	} | 
 |  | 
 | 	val = readw(virtbase + U300_WDOG_SR); | 
 | 	switch (val) { | 
 | 	case U300_WDOG_SR_STATUS_TIMED_OUT: | 
 | 		dev_info(dev, "watchdog timed out since last chip reset!\n"); | 
 | 		coh901327_wdt.bootstatus |= WDIOF_CARDRESET; | 
 | 		/* Status will be cleared below */ | 
 | 		break; | 
 | 	case U300_WDOG_SR_STATUS_NORMAL: | 
 | 		dev_info(dev, "in normal status, no timeouts have occurred.\n"); | 
 | 		break; | 
 | 	default: | 
 | 		dev_info(dev, "contains an illegal status code (%08x)\n", val); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	val = readw(virtbase + U300_WDOG_D2R); | 
 | 	switch (val) { | 
 | 	case U300_WDOG_D2R_DISABLE_STATUS_DISABLED: | 
 | 		dev_info(dev, "currently disabled.\n"); | 
 | 		break; | 
 | 	case U300_WDOG_D2R_DISABLE_STATUS_ENABLED: | 
 | 		dev_info(dev, "currently enabled! (disabling it now)\n"); | 
 | 		coh901327_disable(); | 
 | 		break; | 
 | 	default: | 
 | 		dev_err(dev, "contains an illegal enable/disable code (%08x)\n", | 
 | 			val); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	/* Reset the watchdog */ | 
 | 	writew(U300_WDOG_SR_RESET_STATUS_RESET, virtbase + U300_WDOG_SR); | 
 |  | 
 | 	irq = platform_get_irq(pdev, 0); | 
 | 	if (request_irq(irq, coh901327_interrupt, 0, | 
 | 			DRV_NAME " Bark", pdev)) { | 
 | 		ret = -EIO; | 
 | 		goto out_no_irq; | 
 | 	} | 
 |  | 
 | 	watchdog_init_timeout(&coh901327_wdt, margin, dev); | 
 |  | 
 | 	coh901327_wdt.parent = dev; | 
 | 	ret = watchdog_register_device(&coh901327_wdt); | 
 | 	if (ret) | 
 | 		goto out_no_wdog; | 
 |  | 
 | 	dev_info(dev, "initialized. (timeout=%d sec)\n", | 
 | 			coh901327_wdt.timeout); | 
 | 	return 0; | 
 |  | 
 | out_no_wdog: | 
 | 	free_irq(irq, pdev); | 
 | out_no_irq: | 
 | 	clk_disable_unprepare(clk); | 
 | out_no_clk_enable: | 
 | 	clk_put(clk); | 
 | 	return ret; | 
 | } | 
 |  | 
 | #ifdef CONFIG_PM | 
 |  | 
 | static u16 wdogenablestore; | 
 | static u16 irqmaskstore; | 
 |  | 
 | static int coh901327_suspend(struct platform_device *pdev, pm_message_t state) | 
 | { | 
 | 	irqmaskstore = readw(virtbase + U300_WDOG_IMR) & 0x0001U; | 
 | 	wdogenablestore = readw(virtbase + U300_WDOG_D2R); | 
 | 	/* If watchdog is on, disable it here and now */ | 
 | 	if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) | 
 | 		coh901327_disable(); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int coh901327_resume(struct platform_device *pdev) | 
 | { | 
 | 	/* Restore the watchdog interrupt */ | 
 | 	writew(irqmaskstore, virtbase + U300_WDOG_IMR); | 
 | 	if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) { | 
 | 		/* Restart the watchdog timer */ | 
 | 		writew(U300_WDOG_RR_RESTART_VALUE_RESUME, | 
 | 		       virtbase + U300_WDOG_RR); | 
 | 		writew(U300_WDOG_FR_FEED_RESTART_TIMER, | 
 | 		       virtbase + U300_WDOG_FR); | 
 | 	} | 
 | 	return 0; | 
 | } | 
 | #else | 
 | #define coh901327_suspend NULL | 
 | #define coh901327_resume  NULL | 
 | #endif | 
 |  | 
 | /* | 
 |  * Mistreating the watchdog is the only way to perform a software reset of the | 
 |  * system on EMP platforms. So we implement this and export a symbol for it. | 
 |  */ | 
 | void coh901327_watchdog_reset(void) | 
 | { | 
 | 	/* Enable even if on JTAG too */ | 
 | 	writew(U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE, | 
 | 	       virtbase + U300_WDOG_JOR); | 
 | 	/* | 
 | 	 * Timeout = 5s, we have to wait for the watchdog reset to | 
 | 	 * actually take place: the watchdog will be reloaded with the | 
 | 	 * default value immediately, so we HAVE to reboot and get back | 
 | 	 * into the kernel in 30s, or the device will reboot again! | 
 | 	 * The boot loader will typically deactivate the watchdog, so we | 
 | 	 * need time enough for the boot loader to get to the point of | 
 | 	 * deactivating the watchdog before it is shut down by it. | 
 | 	 * | 
 | 	 * NOTE: on future versions of the watchdog, this restriction is | 
 | 	 * gone: the watchdog will be reloaded with a default value (1 min) | 
 | 	 * instead of last value, and you can conveniently set the watchdog | 
 | 	 * timeout to 10ms (value = 1) without any problems. | 
 | 	 */ | 
 | 	coh901327_enable(500); | 
 | 	/* Return and await doom */ | 
 | } | 
 |  | 
 | static const struct of_device_id coh901327_dt_match[] = { | 
 | 	{ .compatible = "stericsson,coh901327" }, | 
 | 	{}, | 
 | }; | 
 |  | 
 | static struct platform_driver coh901327_driver = { | 
 | 	.driver = { | 
 | 		.name	= "coh901327_wdog", | 
 | 		.of_match_table = coh901327_dt_match, | 
 | 	}, | 
 | 	.remove		= __exit_p(coh901327_remove), | 
 | 	.suspend	= coh901327_suspend, | 
 | 	.resume		= coh901327_resume, | 
 | }; | 
 |  | 
 | module_platform_driver_probe(coh901327_driver, coh901327_probe); | 
 |  | 
 | MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); | 
 | MODULE_DESCRIPTION("COH 901 327 Watchdog"); | 
 |  | 
 | module_param(margin, uint, 0); | 
 | MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); | 
 |  | 
 | MODULE_LICENSE("GPL v2"); | 
 | MODULE_ALIAS("platform:coh901327-watchdog"); |