cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ts4800_wdt.c (5148B)


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