brintos

brintos / linux-shallow public Read only

0
0
Text · 4.9 KiB · 24b1ad5 Raw
206 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Watchdog driver for TS-4800 based boards4 *5 * Copyright (c) 2015 - Savoir-faire Linux6 *7 */8 9#include <linux/kernel.h>10#include <linux/mfd/syscon.h>11#include <linux/module.h>12#include <linux/of.h>13#include <linux/platform_device.h>14#include <linux/regmap.h>15#include <linux/watchdog.h>16 17static bool nowayout = WATCHDOG_NOWAYOUT;18module_param(nowayout, bool, 0);19MODULE_PARM_DESC(nowayout,20	"Watchdog cannot be stopped once started (default="21	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");22 23/* possible feed values */24#define TS4800_WDT_FEED_2S       0x125#define TS4800_WDT_FEED_10S      0x226#define TS4800_WDT_DISABLE       0x327 28struct ts4800_wdt {29	struct watchdog_device  wdd;30	struct regmap           *regmap;31	u32                     feed_offset;32	u32                     feed_val;33};34 35/*36 * TS-4800 supports the following timeout values:37 *38 *   value desc39 *   ---------------------40 *     0    feed for 338ms41 *     1    feed for 2.706s42 *     2    feed for 10.824s43 *     3    disable watchdog44 *45 * Keep the regmap/timeout map ordered by timeout46 */47static const struct {48	const int timeout;49	const int regval;50} ts4800_wdt_map[] = {51	{ 2,  TS4800_WDT_FEED_2S },52	{ 10, TS4800_WDT_FEED_10S },53};54 55#define MAX_TIMEOUT_INDEX       (ARRAY_SIZE(ts4800_wdt_map) - 1)56 57static void ts4800_write_feed(struct ts4800_wdt *wdt, u32 val)58{59	regmap_write(wdt->regmap, wdt->feed_offset, val);60}61 62static int ts4800_wdt_start(struct watchdog_device *wdd)63{64	struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);65 66	ts4800_write_feed(wdt, wdt->feed_val);67	return 0;68}69 70static int ts4800_wdt_stop(struct watchdog_device *wdd)71{72	struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);73 74	ts4800_write_feed(wdt, TS4800_WDT_DISABLE);75	return 0;76}77 78static int ts4800_wdt_set_timeout(struct watchdog_device *wdd,79				  unsigned int timeout)80{81	struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);82	int i;83 84	for (i = 0; i < MAX_TIMEOUT_INDEX; i++) {85		if (ts4800_wdt_map[i].timeout >= timeout)86			break;87	}88 89	wdd->timeout = ts4800_wdt_map[i].timeout;90	wdt->feed_val = ts4800_wdt_map[i].regval;91 92	return 0;93}94 95static const struct watchdog_ops ts4800_wdt_ops = {96	.owner = THIS_MODULE,97	.start = ts4800_wdt_start,98	.stop = ts4800_wdt_stop,99	.set_timeout = ts4800_wdt_set_timeout,100};101 102static const struct watchdog_info ts4800_wdt_info = {103	.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,104	.identity = "TS-4800 Watchdog",105};106 107static int ts4800_wdt_probe(struct platform_device *pdev)108{109	struct device *dev = &pdev->dev;110	struct device_node *np = dev->of_node;111	struct device_node *syscon_np;112	struct watchdog_device *wdd;113	struct ts4800_wdt *wdt;114	u32 reg;115	int ret;116 117	syscon_np = of_parse_phandle(np, "syscon", 0);118	if (!syscon_np) {119		dev_err(dev, "no syscon property\n");120		return -ENODEV;121	}122 123	ret = of_property_read_u32_index(np, "syscon", 1, &reg);124	if (ret < 0) {125		dev_err(dev, "no offset in syscon\n");126		of_node_put(syscon_np);127		return ret;128	}129 130	/* allocate memory for watchdog struct */131	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);132	if (!wdt) {133		of_node_put(syscon_np);134		return -ENOMEM;135	}136 137	/* set regmap and offset to know where to write */138	wdt->feed_offset = reg;139	wdt->regmap = syscon_node_to_regmap(syscon_np);140	of_node_put(syscon_np);141	if (IS_ERR(wdt->regmap)) {142		dev_err(dev, "cannot get parent's regmap\n");143		return PTR_ERR(wdt->regmap);144	}145 146	/* Initialize struct watchdog_device */147	wdd = &wdt->wdd;148	wdd->parent = dev;149	wdd->info = &ts4800_wdt_info;150	wdd->ops = &ts4800_wdt_ops;151	wdd->min_timeout = ts4800_wdt_map[0].timeout;152	wdd->max_timeout = ts4800_wdt_map[MAX_TIMEOUT_INDEX].timeout;153 154	watchdog_set_drvdata(wdd, wdt);155	watchdog_set_nowayout(wdd, nowayout);156	watchdog_init_timeout(wdd, 0, dev);157 158	/*159	 * As this watchdog supports only a few values, ts4800_wdt_set_timeout160	 * must be called to initialize timeout and feed_val with valid values.161	 * Default to maximum timeout if none, or an invalid one, is provided in162	 * device tree.163	 */164	if (!wdd->timeout)165		wdd->timeout = wdd->max_timeout;166	ts4800_wdt_set_timeout(wdd, wdd->timeout);167 168	/*169	 * The feed register is write-only, so it is not possible to determine170	 * watchdog's state. Disable it to be in a known state.171	 */172	ts4800_wdt_stop(wdd);173 174	ret = devm_watchdog_register_device(dev, wdd);175	if (ret)176		return ret;177 178	platform_set_drvdata(pdev, wdt);179 180	dev_info(dev, "initialized (timeout = %d sec, nowayout = %d)\n",181		 wdd->timeout, nowayout);182 183	return 0;184}185 186static const struct of_device_id ts4800_wdt_of_match[] = {187	{ .compatible = "technologic,ts4800-wdt", },188	{ },189};190MODULE_DEVICE_TABLE(of, ts4800_wdt_of_match);191 192static struct platform_driver ts4800_wdt_driver = {193	.probe		= ts4800_wdt_probe,194	.driver		= {195		.name	= "ts4800_wdt",196		.of_match_table = ts4800_wdt_of_match,197	},198};199 200module_platform_driver(ts4800_wdt_driver);201 202MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>");203MODULE_DESCRIPTION("Watchdog driver for TS-4800 based boards");204MODULE_LICENSE("GPL v2");205MODULE_ALIAS("platform:ts4800_wdt");206