Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * Watchdog driver for TS-4800 based boards
0003  *
0004  * Copyright (c) 2015 - Savoir-faire Linux
0005  *
0006  * This file is licensed under the terms of the GNU General Public
0007  * License version 2. This program is licensed "as is" without any
0008  * warranty of any kind, whether express or implied.
0009  */
0010 
0011 #include <linux/kernel.h>
0012 #include <linux/mfd/syscon.h>
0013 #include <linux/module.h>
0014 #include <linux/of.h>
0015 #include <linux/platform_device.h>
0016 #include <linux/regmap.h>
0017 #include <linux/watchdog.h>
0018 
0019 static bool nowayout = WATCHDOG_NOWAYOUT;
0020 module_param(nowayout, bool, 0);
0021 MODULE_PARM_DESC(nowayout,
0022     "Watchdog cannot be stopped once started (default="
0023     __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0024 
0025 /* possible feed values */
0026 #define TS4800_WDT_FEED_2S       0x1
0027 #define TS4800_WDT_FEED_10S      0x2
0028 #define TS4800_WDT_DISABLE       0x3
0029 
0030 struct ts4800_wdt {
0031     struct watchdog_device  wdd;
0032     struct regmap           *regmap;
0033     u32                     feed_offset;
0034     u32                     feed_val;
0035 };
0036 
0037 /*
0038  * TS-4800 supports the following timeout values:
0039  *
0040  *   value desc
0041  *   ---------------------
0042  *     0    feed for 338ms
0043  *     1    feed for 2.706s
0044  *     2    feed for 10.824s
0045  *     3    disable watchdog
0046  *
0047  * Keep the regmap/timeout map ordered by timeout
0048  */
0049 static const struct {
0050     const int timeout;
0051     const int regval;
0052 } ts4800_wdt_map[] = {
0053     { 2,  TS4800_WDT_FEED_2S },
0054     { 10, TS4800_WDT_FEED_10S },
0055 };
0056 
0057 #define MAX_TIMEOUT_INDEX       (ARRAY_SIZE(ts4800_wdt_map) - 1)
0058 
0059 static void ts4800_write_feed(struct ts4800_wdt *wdt, u32 val)
0060 {
0061     regmap_write(wdt->regmap, wdt->feed_offset, val);
0062 }
0063 
0064 static int ts4800_wdt_start(struct watchdog_device *wdd)
0065 {
0066     struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
0067 
0068     ts4800_write_feed(wdt, wdt->feed_val);
0069     return 0;
0070 }
0071 
0072 static int ts4800_wdt_stop(struct watchdog_device *wdd)
0073 {
0074     struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
0075 
0076     ts4800_write_feed(wdt, TS4800_WDT_DISABLE);
0077     return 0;
0078 }
0079 
0080 static int ts4800_wdt_set_timeout(struct watchdog_device *wdd,
0081                   unsigned int timeout)
0082 {
0083     struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
0084     int i;
0085 
0086     for (i = 0; i < MAX_TIMEOUT_INDEX; i++) {
0087         if (ts4800_wdt_map[i].timeout >= timeout)
0088             break;
0089     }
0090 
0091     wdd->timeout = ts4800_wdt_map[i].timeout;
0092     wdt->feed_val = ts4800_wdt_map[i].regval;
0093 
0094     return 0;
0095 }
0096 
0097 static const struct watchdog_ops ts4800_wdt_ops = {
0098     .owner = THIS_MODULE,
0099     .start = ts4800_wdt_start,
0100     .stop = ts4800_wdt_stop,
0101     .set_timeout = ts4800_wdt_set_timeout,
0102 };
0103 
0104 static const struct watchdog_info ts4800_wdt_info = {
0105     .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
0106     .identity = "TS-4800 Watchdog",
0107 };
0108 
0109 static int ts4800_wdt_probe(struct platform_device *pdev)
0110 {
0111     struct device *dev = &pdev->dev;
0112     struct device_node *np = dev->of_node;
0113     struct device_node *syscon_np;
0114     struct watchdog_device *wdd;
0115     struct ts4800_wdt *wdt;
0116     u32 reg;
0117     int ret;
0118 
0119     syscon_np = of_parse_phandle(np, "syscon", 0);
0120     if (!syscon_np) {
0121         dev_err(dev, "no syscon property\n");
0122         return -ENODEV;
0123     }
0124 
0125     ret = of_property_read_u32_index(np, "syscon", 1, &reg);
0126     if (ret < 0) {
0127         dev_err(dev, "no offset in syscon\n");
0128         of_node_put(syscon_np);
0129         return ret;
0130     }
0131 
0132     /* allocate memory for watchdog struct */
0133     wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
0134     if (!wdt) {
0135         of_node_put(syscon_np);
0136         return -ENOMEM;
0137     }
0138 
0139     /* set regmap and offset to know where to write */
0140     wdt->feed_offset = reg;
0141     wdt->regmap = syscon_node_to_regmap(syscon_np);
0142     of_node_put(syscon_np);
0143     if (IS_ERR(wdt->regmap)) {
0144         dev_err(dev, "cannot get parent's regmap\n");
0145         return PTR_ERR(wdt->regmap);
0146     }
0147 
0148     /* Initialize struct watchdog_device */
0149     wdd = &wdt->wdd;
0150     wdd->parent = dev;
0151     wdd->info = &ts4800_wdt_info;
0152     wdd->ops = &ts4800_wdt_ops;
0153     wdd->min_timeout = ts4800_wdt_map[0].timeout;
0154     wdd->max_timeout = ts4800_wdt_map[MAX_TIMEOUT_INDEX].timeout;
0155 
0156     watchdog_set_drvdata(wdd, wdt);
0157     watchdog_set_nowayout(wdd, nowayout);
0158     watchdog_init_timeout(wdd, 0, dev);
0159 
0160     /*
0161      * As this watchdog supports only a few values, ts4800_wdt_set_timeout
0162      * must be called to initialize timeout and feed_val with valid values.
0163      * Default to maximum timeout if none, or an invalid one, is provided in
0164      * device tree.
0165      */
0166     if (!wdd->timeout)
0167         wdd->timeout = wdd->max_timeout;
0168     ts4800_wdt_set_timeout(wdd, wdd->timeout);
0169 
0170     /*
0171      * The feed register is write-only, so it is not possible to determine
0172      * watchdog's state. Disable it to be in a known state.
0173      */
0174     ts4800_wdt_stop(wdd);
0175 
0176     ret = devm_watchdog_register_device(dev, wdd);
0177     if (ret)
0178         return ret;
0179 
0180     platform_set_drvdata(pdev, wdt);
0181 
0182     dev_info(dev, "initialized (timeout = %d sec, nowayout = %d)\n",
0183          wdd->timeout, nowayout);
0184 
0185     return 0;
0186 }
0187 
0188 static const struct of_device_id ts4800_wdt_of_match[] = {
0189     { .compatible = "technologic,ts4800-wdt", },
0190     { },
0191 };
0192 MODULE_DEVICE_TABLE(of, ts4800_wdt_of_match);
0193 
0194 static struct platform_driver ts4800_wdt_driver = {
0195     .probe      = ts4800_wdt_probe,
0196     .driver     = {
0197         .name   = "ts4800_wdt",
0198         .of_match_table = ts4800_wdt_of_match,
0199     },
0200 };
0201 
0202 module_platform_driver(ts4800_wdt_driver);
0203 
0204 MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>");
0205 MODULE_LICENSE("GPL v2");
0206 MODULE_ALIAS("platform:ts4800_wdt");