brintos

brintos / linux-shallow public Read only

0
0
Text · 8.6 KiB · 12c41d6 Raw
347 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Watchdog driver for Renesas WDT watchdog4 *5 * Copyright (C) 2015-17 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>6 * Copyright (C) 2015-17 Renesas Electronics Corporation7 */8#include <linux/bitops.h>9#include <linux/clk.h>10#include <linux/delay.h>11#include <linux/io.h>12#include <linux/iopoll.h>13#include <linux/kernel.h>14#include <linux/module.h>15#include <linux/of.h>16#include <linux/platform_device.h>17#include <linux/pm_runtime.h>18#include <linux/smp.h>19#include <linux/sys_soc.h>20#include <linux/watchdog.h>21 22#define RWTCNT		023#define RWTCSRA		424#define RWTCSRA_WOVF	BIT(4)25#define RWTCSRA_WRFLG	BIT(5)26#define RWTCSRA_TME	BIT(7)27#define RWTCSRB		828 29#define RWDT_DEFAULT_TIMEOUT 60U30 31/*32 * In probe, clk_rate is checked to be not more than 16 bit * biggest clock33 * divider (12 bits). d is only a factor to fully utilize the WDT counter and34 * will not exceed its 16 bits. Thus, no overflow, we stay below 32 bits.35 */36#define MUL_BY_CLKS_PER_SEC(p, d) \37	DIV_ROUND_UP((d) * (p)->clk_rate, clk_divs[(p)->cks])38 39/* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */40#define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate)41 42static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024, 4096 };43 44static bool nowayout = WATCHDOG_NOWAYOUT;45module_param(nowayout, bool, 0);46MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="47				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");48 49struct rwdt_priv {50	void __iomem *base;51	struct watchdog_device wdev;52	unsigned long clk_rate;53	u8 cks;54	struct clk *clk;55};56 57static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned int reg)58{59	if (reg == RWTCNT)60		val |= 0x5a5a0000;61	else62		val |= 0xa5a5a500;63 64	writel_relaxed(val, priv->base + reg);65}66 67static int rwdt_init_timeout(struct watchdog_device *wdev)68{69	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);70 71	rwdt_write(priv, 65536 - MUL_BY_CLKS_PER_SEC(priv, wdev->timeout), RWTCNT);72 73	return 0;74}75 76static void rwdt_wait_cycles(struct rwdt_priv *priv, unsigned int cycles)77{78	unsigned int delay;79 80	delay = DIV_ROUND_UP(cycles * 1000000, priv->clk_rate);81 82	usleep_range(delay, 2 * delay);83}84 85static int rwdt_start(struct watchdog_device *wdev)86{87	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);88	u8 val;89 90	pm_runtime_get_sync(wdev->parent);91 92	/* Stop the timer before we modify any register */93	val = readb_relaxed(priv->base + RWTCSRA) & ~RWTCSRA_TME;94	rwdt_write(priv, val, RWTCSRA);95	/* Delay 2 cycles before setting watchdog counter */96	rwdt_wait_cycles(priv, 2);97 98	rwdt_init_timeout(wdev);99	rwdt_write(priv, priv->cks, RWTCSRA);100	rwdt_write(priv, 0, RWTCSRB);101 102	while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)103		cpu_relax();104 105	rwdt_write(priv, priv->cks | RWTCSRA_TME, RWTCSRA);106 107	return 0;108}109 110static int rwdt_stop(struct watchdog_device *wdev)111{112	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);113 114	rwdt_write(priv, priv->cks, RWTCSRA);115	/* Delay 3 cycles before disabling module clock */116	rwdt_wait_cycles(priv, 3);117	pm_runtime_put(wdev->parent);118 119	return 0;120}121 122static unsigned int rwdt_get_timeleft(struct watchdog_device *wdev)123{124	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);125	u16 val = readw_relaxed(priv->base + RWTCNT);126 127	return DIV_BY_CLKS_PER_SEC(priv, 65536 - val);128}129 130/* needs to be atomic - no RPM, no usleep_range, no scheduling! */131static int rwdt_restart(struct watchdog_device *wdev, unsigned long action,132			void *data)133{134	struct rwdt_priv *priv = watchdog_get_drvdata(wdev);135	u8 val;136 137	clk_prepare_enable(priv->clk);138 139	/* Stop the timer before we modify any register */140	val = readb_relaxed(priv->base + RWTCSRA) & ~RWTCSRA_TME;141	rwdt_write(priv, val, RWTCSRA);142	/* Delay 2 cycles before setting watchdog counter */143	udelay(DIV_ROUND_UP(2 * 1000000, priv->clk_rate));144 145	rwdt_write(priv, 0xffff, RWTCNT);146	/* smallest divider to reboot soon */147	rwdt_write(priv, 0, RWTCSRA);148 149	readb_poll_timeout_atomic(priv->base + RWTCSRA, val,150				  !(val & RWTCSRA_WRFLG), 1, 100);151 152	rwdt_write(priv, RWTCSRA_TME, RWTCSRA);153 154	/* wait 2 cycles, so watchdog will trigger */155	udelay(DIV_ROUND_UP(2 * 1000000, priv->clk_rate));156 157	return 0;158}159 160static const struct watchdog_info rwdt_ident = {161	.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |162		WDIOF_CARDRESET,163	.identity = "Renesas WDT Watchdog",164};165 166static const struct watchdog_ops rwdt_ops = {167	.owner = THIS_MODULE,168	.start = rwdt_start,169	.stop = rwdt_stop,170	.ping = rwdt_init_timeout,171	.get_timeleft = rwdt_get_timeleft,172	.restart = rwdt_restart,173};174 175#if defined(CONFIG_ARCH_RCAR_GEN2) && defined(CONFIG_SMP)176/*177 * Watchdog-reset integration is broken on early revisions of R-Car Gen2 SoCs178 */179static const struct soc_device_attribute rwdt_quirks_match[] = {180	{181		.soc_id = "r8a7790",182		.revision = "ES1.*",183		.data = (void *)1,	/* needs single CPU */184	}, {185		.soc_id = "r8a7791",186		.revision = "ES1.*",187		.data = (void *)1,	/* needs single CPU */188	}, {189		.soc_id = "r8a7792",190		.data = (void *)0,	/* needs SMP disabled */191	},192	{ /* sentinel */ }193};194 195static bool rwdt_blacklisted(struct device *dev)196{197	const struct soc_device_attribute *attr;198 199	attr = soc_device_match(rwdt_quirks_match);200	if (attr && setup_max_cpus > (uintptr_t)attr->data) {201		dev_info(dev, "Watchdog blacklisted on %s %s\n", attr->soc_id,202			 attr->revision);203		return true;204	}205 206	return false;207}208#else /* !CONFIG_ARCH_RCAR_GEN2 || !CONFIG_SMP */209static inline bool rwdt_blacklisted(struct device *dev) { return false; }210#endif /* !CONFIG_ARCH_RCAR_GEN2 || !CONFIG_SMP */211 212static int rwdt_probe(struct platform_device *pdev)213{214	struct device *dev = &pdev->dev;215	struct rwdt_priv *priv;216	unsigned long clks_per_sec;217	int ret, i;218	u8 csra;219 220	if (rwdt_blacklisted(dev))221		return -ENODEV;222 223	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);224	if (!priv)225		return -ENOMEM;226 227	priv->base = devm_platform_ioremap_resource(pdev, 0);228	if (IS_ERR(priv->base))229		return PTR_ERR(priv->base);230 231	priv->clk = devm_clk_get(dev, NULL);232	if (IS_ERR(priv->clk))233		return PTR_ERR(priv->clk);234 235	pm_runtime_enable(dev);236	pm_runtime_get_sync(dev);237	priv->clk_rate = clk_get_rate(priv->clk);238	csra = readb_relaxed(priv->base + RWTCSRA);239	priv->wdev.bootstatus = csra & RWTCSRA_WOVF ? WDIOF_CARDRESET : 0;240	pm_runtime_put(dev);241 242	if (!priv->clk_rate) {243		ret = -ENOENT;244		goto out_pm_disable;245	}246 247	for (i = ARRAY_SIZE(clk_divs) - 1; i >= 0; i--) {248		clks_per_sec = priv->clk_rate / clk_divs[i];249		if (clks_per_sec && clks_per_sec < 65536) {250			priv->cks = i;251			break;252		}253	}254 255	if (i < 0) {256		dev_err(dev, "Can't find suitable clock divider\n");257		ret = -ERANGE;258		goto out_pm_disable;259	}260 261	priv->wdev.info = &rwdt_ident;262	priv->wdev.ops = &rwdt_ops;263	priv->wdev.parent = dev;264	priv->wdev.min_timeout = 1;265	priv->wdev.max_timeout = DIV_BY_CLKS_PER_SEC(priv, 65536);266	priv->wdev.timeout = min(priv->wdev.max_timeout, RWDT_DEFAULT_TIMEOUT);267 268	platform_set_drvdata(pdev, priv);269	watchdog_set_drvdata(&priv->wdev, priv);270	watchdog_set_nowayout(&priv->wdev, nowayout);271	watchdog_set_restart_priority(&priv->wdev, 0);272	watchdog_stop_on_unregister(&priv->wdev);273 274	/* This overrides the default timeout only if DT configuration was found */275	watchdog_init_timeout(&priv->wdev, 0, dev);276 277	/* Check if FW enabled the watchdog */278	if (csra & RWTCSRA_TME) {279		/* Ensure properly initialized dividers */280		rwdt_start(&priv->wdev);281		set_bit(WDOG_HW_RUNNING, &priv->wdev.status);282	}283 284	ret = watchdog_register_device(&priv->wdev);285	if (ret < 0)286		goto out_pm_disable;287 288	return 0;289 290 out_pm_disable:291	pm_runtime_disable(dev);292	return ret;293}294 295static void rwdt_remove(struct platform_device *pdev)296{297	struct rwdt_priv *priv = platform_get_drvdata(pdev);298 299	watchdog_unregister_device(&priv->wdev);300	pm_runtime_disable(&pdev->dev);301}302 303static int __maybe_unused rwdt_suspend(struct device *dev)304{305	struct rwdt_priv *priv = dev_get_drvdata(dev);306 307	if (watchdog_active(&priv->wdev))308		rwdt_stop(&priv->wdev);309 310	return 0;311}312 313static int __maybe_unused rwdt_resume(struct device *dev)314{315	struct rwdt_priv *priv = dev_get_drvdata(dev);316 317	if (watchdog_active(&priv->wdev))318		rwdt_start(&priv->wdev);319 320	return 0;321}322 323static SIMPLE_DEV_PM_OPS(rwdt_pm_ops, rwdt_suspend, rwdt_resume);324 325static const struct of_device_id rwdt_ids[] = {326	{ .compatible = "renesas,rcar-gen2-wdt", },327	{ .compatible = "renesas,rcar-gen3-wdt", },328	{ .compatible = "renesas,rcar-gen4-wdt", },329	{ /* sentinel */ }330};331MODULE_DEVICE_TABLE(of, rwdt_ids);332 333static struct platform_driver rwdt_driver = {334	.driver = {335		.name = "renesas_wdt",336		.of_match_table = rwdt_ids,337		.pm = &rwdt_pm_ops,338	},339	.probe = rwdt_probe,340	.remove_new = rwdt_remove,341};342module_platform_driver(rwdt_driver);343 344MODULE_DESCRIPTION("Renesas WDT Watchdog Driver");345MODULE_LICENSE("GPL v2");346MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>");347