brintos

brintos / linux-shallow public Read only

0
0
Text · 8.4 KiB · 45047e5 Raw
377 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Watchdog driver for Alphascale ASM9260.4 *5 * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>6 */7 8#include <linux/bitops.h>9#include <linux/clk.h>10#include <linux/delay.h>11#include <linux/interrupt.h>12#include <linux/io.h>13#include <linux/module.h>14#include <linux/of.h>15#include <linux/platform_device.h>16#include <linux/reset.h>17#include <linux/watchdog.h>18 19#define CLOCK_FREQ	100000020 21/* Watchdog Mode register */22#define HW_WDMOD			0x0023/* Wake interrupt. Set by HW, can't be cleared. */24#define BM_MOD_WDINT			BIT(3)25/* This bit set if timeout reached. Cleared by SW. */26#define BM_MOD_WDTOF			BIT(2)27/* HW Reset on timeout */28#define BM_MOD_WDRESET			BIT(1)29/* WD enable */30#define BM_MOD_WDEN			BIT(0)31 32/*33 * Watchdog Timer Constant register34 * Minimal value is 0xff, the meaning of this value35 * depends on used clock: T = WDCLK * (0xff + 1) * 436 */37#define HW_WDTC				0x0438#define BM_WDTC_MAX(freq)		(0x7fffffff / (freq))39 40/* Watchdog Feed register */41#define HW_WDFEED			0x0842 43/* Watchdog Timer Value register */44#define HW_WDTV				0x0c45 46#define ASM9260_WDT_DEFAULT_TIMEOUT	3047 48enum asm9260_wdt_mode {49	HW_RESET,50	SW_RESET,51	DEBUG,52};53 54struct asm9260_wdt_priv {55	struct device		*dev;56	struct watchdog_device	wdd;57	struct clk		*clk;58	struct clk		*clk_ahb;59	struct reset_control	*rst;60 61	void __iomem		*iobase;62	int			irq;63	unsigned long		wdt_freq;64	enum asm9260_wdt_mode	mode;65};66 67static int asm9260_wdt_feed(struct watchdog_device *wdd)68{69	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);70 71	iowrite32(0xaa, priv->iobase + HW_WDFEED);72	iowrite32(0x55, priv->iobase + HW_WDFEED);73 74	return 0;75}76 77static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)78{79	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);80	u32 counter;81 82	counter = ioread32(priv->iobase + HW_WDTV);83 84	return counter / priv->wdt_freq;85}86 87static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)88{89	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);90	u32 counter;91 92	counter = wdd->timeout * priv->wdt_freq;93 94	iowrite32(counter, priv->iobase + HW_WDTC);95 96	return 0;97}98 99static int asm9260_wdt_enable(struct watchdog_device *wdd)100{101	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);102	u32 mode = 0;103 104	if (priv->mode == HW_RESET)105		mode = BM_MOD_WDRESET;106 107	iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);108 109	asm9260_wdt_updatetimeout(wdd);110 111	asm9260_wdt_feed(wdd);112 113	return 0;114}115 116static int asm9260_wdt_disable(struct watchdog_device *wdd)117{118	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);119 120	/* The only way to disable WD is to reset it. */121	reset_control_assert(priv->rst);122	reset_control_deassert(priv->rst);123 124	return 0;125}126 127static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)128{129	wdd->timeout = to;130	asm9260_wdt_updatetimeout(wdd);131 132	return 0;133}134 135static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)136{137	/* init WD if it was not started */138 139	iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);140 141	iowrite32(0xff, priv->iobase + HW_WDTC);142	/* first pass correct sequence */143	asm9260_wdt_feed(&priv->wdd);144	/*145	 * Then write wrong pattern to the feed to trigger reset146	 * ASAP.147	 */148	iowrite32(0xff, priv->iobase + HW_WDFEED);149 150	mdelay(1000);151}152 153static irqreturn_t asm9260_wdt_irq(int irq, void *devid)154{155	struct asm9260_wdt_priv *priv = devid;156	u32 stat;157 158	stat = ioread32(priv->iobase + HW_WDMOD);159	if (!(stat & BM_MOD_WDINT))160		return IRQ_NONE;161 162	if (priv->mode == DEBUG) {163		dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");164	} else {165		dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");166		asm9260_wdt_sys_reset(priv);167	}168 169	return IRQ_HANDLED;170}171 172static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,173			   void *data)174{175	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);176 177	asm9260_wdt_sys_reset(priv);178 179	return 0;180}181 182static const struct watchdog_info asm9260_wdt_ident = {183	.options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING184				| WDIOF_MAGICCLOSE,185	.identity         =	"Alphascale asm9260 Watchdog",186};187 188static const struct watchdog_ops asm9260_wdt_ops = {189	.owner		= THIS_MODULE,190	.start		= asm9260_wdt_enable,191	.stop		= asm9260_wdt_disable,192	.get_timeleft	= asm9260_wdt_gettimeleft,193	.ping		= asm9260_wdt_feed,194	.set_timeout	= asm9260_wdt_settimeout,195	.restart	= asm9260_restart,196};197 198static void asm9260_clk_disable_unprepare(void *data)199{200	clk_disable_unprepare(data);201}202 203static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)204{205	int err;206	unsigned long clk;207 208	priv->clk = devm_clk_get(priv->dev, "mod");209	if (IS_ERR(priv->clk)) {210		dev_err(priv->dev, "Failed to get \"mod\" clk\n");211		return PTR_ERR(priv->clk);212	}213 214	/* configure AHB clock */215	priv->clk_ahb = devm_clk_get(priv->dev, "ahb");216	if (IS_ERR(priv->clk_ahb)) {217		dev_err(priv->dev, "Failed to get \"ahb\" clk\n");218		return PTR_ERR(priv->clk_ahb);219	}220 221	err = clk_prepare_enable(priv->clk_ahb);222	if (err) {223		dev_err(priv->dev, "Failed to enable ahb_clk!\n");224		return err;225	}226	err = devm_add_action_or_reset(priv->dev,227				       asm9260_clk_disable_unprepare,228				       priv->clk_ahb);229	if (err)230		return err;231 232	err = clk_set_rate(priv->clk, CLOCK_FREQ);233	if (err) {234		dev_err(priv->dev, "Failed to set rate!\n");235		return err;236	}237 238	err = clk_prepare_enable(priv->clk);239	if (err) {240		dev_err(priv->dev, "Failed to enable clk!\n");241		return err;242	}243	err = devm_add_action_or_reset(priv->dev,244				       asm9260_clk_disable_unprepare,245				       priv->clk);246	if (err)247		return err;248 249	/* wdt has internal divider */250	clk = clk_get_rate(priv->clk);251	if (!clk) {252		dev_err(priv->dev, "Failed, clk is 0!\n");253		return -EINVAL;254	}255 256	priv->wdt_freq = clk / 2;257 258	return 0;259}260 261static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)262{263	const char *tmp;264	int ret;265 266	/* default mode */267	priv->mode = HW_RESET;268 269	ret = of_property_read_string(priv->dev->of_node,270				      "alphascale,mode", &tmp);271	if (ret < 0)272		return;273 274	if (!strcmp(tmp, "hw"))275		priv->mode = HW_RESET;276	else if (!strcmp(tmp, "sw"))277		priv->mode = SW_RESET;278	else if (!strcmp(tmp, "debug"))279		priv->mode = DEBUG;280	else281		dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",282			 tmp);283}284 285static int asm9260_wdt_probe(struct platform_device *pdev)286{287	struct device *dev = &pdev->dev;288	struct asm9260_wdt_priv *priv;289	struct watchdog_device *wdd;290	int ret;291	static const char * const mode_name[] = { "hw", "sw", "debug", };292 293	priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL);294	if (!priv)295		return -ENOMEM;296 297	priv->dev = dev;298 299	priv->iobase = devm_platform_ioremap_resource(pdev, 0);300	if (IS_ERR(priv->iobase))301		return PTR_ERR(priv->iobase);302 303	priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst");304	if (IS_ERR(priv->rst))305		return PTR_ERR(priv->rst);306 307	ret = asm9260_wdt_get_dt_clks(priv);308	if (ret)309		return ret;310 311	wdd = &priv->wdd;312	wdd->info = &asm9260_wdt_ident;313	wdd->ops = &asm9260_wdt_ops;314	wdd->min_timeout = 1;315	wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);316	wdd->parent = dev;317 318	watchdog_set_drvdata(wdd, priv);319 320	/*321	 * If 'timeout-sec' unspecified in devicetree, assume a 30 second322	 * default, unless the max timeout is less than 30 seconds, then use323	 * the max instead.324	 */325	wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;326	watchdog_init_timeout(wdd, 0, dev);327 328	asm9260_wdt_get_dt_mode(priv);329 330	if (priv->mode != HW_RESET)331		priv->irq = platform_get_irq(pdev, 0);332 333	if (priv->irq > 0) {334		/*335		 * Not all supported platforms specify an interrupt for the336		 * watchdog, so let's make it optional.337		 */338		ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0,339				       pdev->name, priv);340		if (ret < 0)341			dev_warn(dev, "failed to request IRQ\n");342	}343 344	watchdog_set_restart_priority(wdd, 128);345 346	watchdog_stop_on_reboot(wdd);347	watchdog_stop_on_unregister(wdd);348	ret = devm_watchdog_register_device(dev, wdd);349	if (ret)350		return ret;351 352	platform_set_drvdata(pdev, priv);353 354	dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",355		 wdd->timeout, mode_name[priv->mode]);356	return 0;357}358 359static const struct of_device_id asm9260_wdt_of_match[] = {360	{ .compatible = "alphascale,asm9260-wdt"},361	{},362};363MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);364 365static struct platform_driver asm9260_wdt_driver = {366	.driver = {367		.name = "asm9260-wdt",368		.of_match_table	= asm9260_wdt_of_match,369	},370	.probe = asm9260_wdt_probe,371};372module_platform_driver(asm9260_wdt_driver);373 374MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");375MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");376MODULE_LICENSE("GPL");377