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, ®);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