Commit 93937669e9b5873808e4f5dfd6cace53bdc57f17

Authored by Naidu Tellapati
Committed by Wim Van Sebroeck
1 parent cc4f9c2a91

watchdog: ImgTec PDC Watchdog Timer Driver

This commit adds support for ImgTec PowerDown Controller Watchdog Timer.

Reviewed-by: Andrew Bresticker <abrestic@chromium.org>
Signed-off-by: Naidu Tellapati <Naidu.Tellapati@imgtec.com>
Signed-off-by: Jude Abraham <Jude.Abraham@imgtec.com>
[ezequiel: Minor style fixes]
Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>

Showing 3 changed files with 301 additions and 0 deletions Side-by-side Diff

drivers/watchdog/Kconfig
... ... @@ -1235,6 +1235,17 @@
1235 1235  
1236 1236 If in doubt, say 'N'.
1237 1237  
  1238 +config IMGPDC_WDT
  1239 + tristate "Imagination Technologies PDC Watchdog Timer"
  1240 + depends on HAS_IOMEM
  1241 + depends on METAG || MIPS || COMPILE_TEST
  1242 + help
  1243 + Driver for Imagination Technologies PowerDown Controller
  1244 + Watchdog Timer.
  1245 +
  1246 + To compile this driver as a loadable module, choose M here.
  1247 + The module will be called imgpdc_wdt.
  1248 +
1238 1249 config LANTIQ_WDT
1239 1250 tristate "Lantiq SoC watchdog"
1240 1251 depends on LANTIQ
drivers/watchdog/Makefile
... ... @@ -142,6 +142,7 @@
142 142 octeon-wdt-y := octeon-wdt-main.o octeon-wdt-nmi.o
143 143 obj-$(CONFIG_LANTIQ_WDT) += lantiq_wdt.o
144 144 obj-$(CONFIG_RALINK_WDT) += rt2880_wdt.o
  145 +obj-$(CONFIG_IMGPDC_WDT) += imgpdc_wdt.o
145 146  
146 147 # PARISC Architecture
147 148  
drivers/watchdog/imgpdc_wdt.c
  1 +/*
  2 + * Imagination Technologies PowerDown Controller Watchdog Timer.
  3 + *
  4 + * Copyright (c) 2014 Imagination Technologies Ltd.
  5 + *
  6 + * This program is free software; you can redistribute it and/or modify it
  7 + * under the terms of the GNU General Public License version 2 as published by
  8 + * the Free Software Foundation.
  9 + *
  10 + * Based on drivers/watchdog/sunxi_wdt.c Copyright (c) 2013 Carlo Caione
  11 + * 2012 Henrik Nordstrom
  12 + */
  13 +
  14 +#include <linux/clk.h>
  15 +#include <linux/io.h>
  16 +#include <linux/log2.h>
  17 +#include <linux/module.h>
  18 +#include <linux/platform_device.h>
  19 +#include <linux/slab.h>
  20 +#include <linux/watchdog.h>
  21 +
  22 +/* registers */
  23 +#define PDC_WDT_SOFT_RESET 0x00
  24 +#define PDC_WDT_CONFIG 0x04
  25 + #define PDC_WDT_CONFIG_ENABLE BIT(31)
  26 + #define PDC_WDT_CONFIG_DELAY_MASK 0x1f
  27 +
  28 +#define PDC_WDT_TICKLE1 0x08
  29 +#define PDC_WDT_TICKLE1_MAGIC 0xabcd1234
  30 +#define PDC_WDT_TICKLE2 0x0c
  31 +#define PDC_WDT_TICKLE2_MAGIC 0x4321dcba
  32 +
  33 +#define PDC_WDT_TICKLE_STATUS_MASK 0x7
  34 +#define PDC_WDT_TICKLE_STATUS_SHIFT 0
  35 +#define PDC_WDT_TICKLE_STATUS_HRESET 0x0 /* Hard reset */
  36 +#define PDC_WDT_TICKLE_STATUS_TIMEOUT 0x1 /* Timeout */
  37 +#define PDC_WDT_TICKLE_STATUS_TICKLE 0x2 /* Tickled incorrectly */
  38 +#define PDC_WDT_TICKLE_STATUS_SRESET 0x3 /* Soft reset */
  39 +#define PDC_WDT_TICKLE_STATUS_USER 0x4 /* User reset */
  40 +
  41 +/* Timeout values are in seconds */
  42 +#define PDC_WDT_MIN_TIMEOUT 1
  43 +#define PDC_WDT_DEF_TIMEOUT 64
  44 +
  45 +static int heartbeat;
  46 +module_param(heartbeat, int, 0);
  47 +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
  48 + "(default = " __MODULE_STRING(PDC_WDT_DEF_TIMEOUT) ")");
  49 +
  50 +static bool nowayout = WATCHDOG_NOWAYOUT;
  51 +module_param(nowayout, bool, 0);
  52 +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  53 + "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  54 +
  55 +struct pdc_wdt_dev {
  56 + struct watchdog_device wdt_dev;
  57 + struct clk *wdt_clk;
  58 + struct clk *sys_clk;
  59 + void __iomem *base;
  60 +};
  61 +
  62 +static int pdc_wdt_keepalive(struct watchdog_device *wdt_dev)
  63 +{
  64 + struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
  65 +
  66 + writel(PDC_WDT_TICKLE1_MAGIC, wdt->base + PDC_WDT_TICKLE1);
  67 + writel(PDC_WDT_TICKLE2_MAGIC, wdt->base + PDC_WDT_TICKLE2);
  68 +
  69 + return 0;
  70 +}
  71 +
  72 +static int pdc_wdt_stop(struct watchdog_device *wdt_dev)
  73 +{
  74 + unsigned int val;
  75 + struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
  76 +
  77 + val = readl(wdt->base + PDC_WDT_CONFIG);
  78 + val &= ~PDC_WDT_CONFIG_ENABLE;
  79 + writel(val, wdt->base + PDC_WDT_CONFIG);
  80 +
  81 + /* Must tickle to finish the stop */
  82 + pdc_wdt_keepalive(wdt_dev);
  83 +
  84 + return 0;
  85 +}
  86 +
  87 +static int pdc_wdt_set_timeout(struct watchdog_device *wdt_dev,
  88 + unsigned int new_timeout)
  89 +{
  90 + unsigned int val;
  91 + struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
  92 + unsigned long clk_rate = clk_get_rate(wdt->wdt_clk);
  93 +
  94 + wdt->wdt_dev.timeout = new_timeout;
  95 +
  96 + val = readl(wdt->base + PDC_WDT_CONFIG) & ~PDC_WDT_CONFIG_DELAY_MASK;
  97 + val |= order_base_2(new_timeout * clk_rate) - 1;
  98 + writel(val, wdt->base + PDC_WDT_CONFIG);
  99 +
  100 + return 0;
  101 +}
  102 +
  103 +/* Start the watchdog timer (delay should already be set) */
  104 +static int pdc_wdt_start(struct watchdog_device *wdt_dev)
  105 +{
  106 + unsigned int val;
  107 + struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
  108 +
  109 + val = readl(wdt->base + PDC_WDT_CONFIG);
  110 + val |= PDC_WDT_CONFIG_ENABLE;
  111 + writel(val, wdt->base + PDC_WDT_CONFIG);
  112 +
  113 + return 0;
  114 +}
  115 +
  116 +static struct watchdog_info pdc_wdt_info = {
  117 + .identity = "IMG PDC Watchdog",
  118 + .options = WDIOF_SETTIMEOUT |
  119 + WDIOF_KEEPALIVEPING |
  120 + WDIOF_MAGICCLOSE,
  121 +};
  122 +
  123 +static const struct watchdog_ops pdc_wdt_ops = {
  124 + .owner = THIS_MODULE,
  125 + .start = pdc_wdt_start,
  126 + .stop = pdc_wdt_stop,
  127 + .ping = pdc_wdt_keepalive,
  128 + .set_timeout = pdc_wdt_set_timeout,
  129 +};
  130 +
  131 +static int pdc_wdt_probe(struct platform_device *pdev)
  132 +{
  133 + int ret, val;
  134 + unsigned long clk_rate;
  135 + struct resource *res;
  136 + struct pdc_wdt_dev *pdc_wdt;
  137 +
  138 + pdc_wdt = devm_kzalloc(&pdev->dev, sizeof(*pdc_wdt), GFP_KERNEL);
  139 + if (!pdc_wdt)
  140 + return -ENOMEM;
  141 +
  142 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  143 + pdc_wdt->base = devm_ioremap_resource(&pdev->dev, res);
  144 + if (IS_ERR(pdc_wdt->base))
  145 + return PTR_ERR(pdc_wdt->base);
  146 +
  147 + pdc_wdt->sys_clk = devm_clk_get(&pdev->dev, "sys");
  148 + if (IS_ERR(pdc_wdt->sys_clk)) {
  149 + dev_err(&pdev->dev, "failed to get the sys clock\n");
  150 + return PTR_ERR(pdc_wdt->sys_clk);
  151 + }
  152 +
  153 + pdc_wdt->wdt_clk = devm_clk_get(&pdev->dev, "wdt");
  154 + if (IS_ERR(pdc_wdt->wdt_clk)) {
  155 + dev_err(&pdev->dev, "failed to get the wdt clock\n");
  156 + return PTR_ERR(pdc_wdt->wdt_clk);
  157 + }
  158 +
  159 + ret = clk_prepare_enable(pdc_wdt->sys_clk);
  160 + if (ret) {
  161 + dev_err(&pdev->dev, "could not prepare or enable sys clock\n");
  162 + return ret;
  163 + }
  164 +
  165 + ret = clk_prepare_enable(pdc_wdt->wdt_clk);
  166 + if (ret) {
  167 + dev_err(&pdev->dev, "could not prepare or enable wdt clock\n");
  168 + goto disable_sys_clk;
  169 + }
  170 +
  171 + /* We use the clock rate to calculate the max timeout */
  172 + clk_rate = clk_get_rate(pdc_wdt->wdt_clk);
  173 + if (clk_rate == 0) {
  174 + dev_err(&pdev->dev, "failed to get clock rate\n");
  175 + ret = -EINVAL;
  176 + goto disable_wdt_clk;
  177 + }
  178 +
  179 + if (order_base_2(clk_rate) > PDC_WDT_CONFIG_DELAY_MASK + 1) {
  180 + dev_err(&pdev->dev, "invalid clock rate\n");
  181 + ret = -EINVAL;
  182 + goto disable_wdt_clk;
  183 + }
  184 +
  185 + if (order_base_2(clk_rate) == 0)
  186 + pdc_wdt->wdt_dev.min_timeout = PDC_WDT_MIN_TIMEOUT + 1;
  187 + else
  188 + pdc_wdt->wdt_dev.min_timeout = PDC_WDT_MIN_TIMEOUT;
  189 +
  190 + pdc_wdt->wdt_dev.info = &pdc_wdt_info;
  191 + pdc_wdt->wdt_dev.ops = &pdc_wdt_ops;
  192 + pdc_wdt->wdt_dev.max_timeout = 1 << PDC_WDT_CONFIG_DELAY_MASK;
  193 + pdc_wdt->wdt_dev.parent = &pdev->dev;
  194 +
  195 + ret = watchdog_init_timeout(&pdc_wdt->wdt_dev, heartbeat, &pdev->dev);
  196 + if (ret < 0) {
  197 + pdc_wdt->wdt_dev.timeout = pdc_wdt->wdt_dev.max_timeout;
  198 + dev_warn(&pdev->dev,
  199 + "Initial timeout out of range! setting max timeout\n");
  200 + }
  201 +
  202 + pdc_wdt_stop(&pdc_wdt->wdt_dev);
  203 +
  204 + /* Find what caused the last reset */
  205 + val = readl(pdc_wdt->base + PDC_WDT_TICKLE1);
  206 + val = (val & PDC_WDT_TICKLE_STATUS_MASK) >> PDC_WDT_TICKLE_STATUS_SHIFT;
  207 + switch (val) {
  208 + case PDC_WDT_TICKLE_STATUS_TICKLE:
  209 + case PDC_WDT_TICKLE_STATUS_TIMEOUT:
  210 + pdc_wdt->wdt_dev.bootstatus |= WDIOF_CARDRESET;
  211 + dev_info(&pdev->dev,
  212 + "watchdog module last reset due to timeout\n");
  213 + break;
  214 + case PDC_WDT_TICKLE_STATUS_HRESET:
  215 + dev_info(&pdev->dev,
  216 + "watchdog module last reset due to hard reset\n");
  217 + break;
  218 + case PDC_WDT_TICKLE_STATUS_SRESET:
  219 + dev_info(&pdev->dev,
  220 + "watchdog module last reset due to soft reset\n");
  221 + break;
  222 + case PDC_WDT_TICKLE_STATUS_USER:
  223 + dev_info(&pdev->dev,
  224 + "watchdog module last reset due to user reset\n");
  225 + break;
  226 + default:
  227 + dev_info(&pdev->dev,
  228 + "contains an illegal status code (%08x)\n", val);
  229 + break;
  230 + }
  231 +
  232 + watchdog_set_nowayout(&pdc_wdt->wdt_dev, nowayout);
  233 +
  234 + platform_set_drvdata(pdev, pdc_wdt);
  235 + watchdog_set_drvdata(&pdc_wdt->wdt_dev, pdc_wdt);
  236 +
  237 + ret = watchdog_register_device(&pdc_wdt->wdt_dev);
  238 + if (ret)
  239 + goto disable_wdt_clk;
  240 +
  241 + return 0;
  242 +
  243 +disable_wdt_clk:
  244 + clk_disable_unprepare(pdc_wdt->wdt_clk);
  245 +disable_sys_clk:
  246 + clk_disable_unprepare(pdc_wdt->sys_clk);
  247 + return ret;
  248 +}
  249 +
  250 +static void pdc_wdt_shutdown(struct platform_device *pdev)
  251 +{
  252 + struct pdc_wdt_dev *pdc_wdt = platform_get_drvdata(pdev);
  253 +
  254 + pdc_wdt_stop(&pdc_wdt->wdt_dev);
  255 +}
  256 +
  257 +static int pdc_wdt_remove(struct platform_device *pdev)
  258 +{
  259 + struct pdc_wdt_dev *pdc_wdt = platform_get_drvdata(pdev);
  260 +
  261 + pdc_wdt_stop(&pdc_wdt->wdt_dev);
  262 + watchdog_unregister_device(&pdc_wdt->wdt_dev);
  263 + clk_disable_unprepare(pdc_wdt->wdt_clk);
  264 + clk_disable_unprepare(pdc_wdt->sys_clk);
  265 +
  266 + return 0;
  267 +}
  268 +
  269 +static const struct of_device_id pdc_wdt_match[] = {
  270 + { .compatible = "img,pdc-wdt" },
  271 + {}
  272 +};
  273 +MODULE_DEVICE_TABLE(of, pdc_wdt_match);
  274 +
  275 +static struct platform_driver pdc_wdt_driver = {
  276 + .driver = {
  277 + .name = "imgpdc-wdt",
  278 + .of_match_table = pdc_wdt_match,
  279 + },
  280 + .probe = pdc_wdt_probe,
  281 + .remove = pdc_wdt_remove,
  282 + .shutdown = pdc_wdt_shutdown,
  283 +};
  284 +module_platform_driver(pdc_wdt_driver);
  285 +
  286 +MODULE_AUTHOR("Jude Abraham <Jude.Abraham@imgtec.com>");
  287 +MODULE_AUTHOR("Naidu Tellapati <Naidu.Tellapati@imgtec.com>");
  288 +MODULE_DESCRIPTION("Imagination Technologies PDC Watchdog Timer Driver");
  289 +MODULE_LICENSE("GPL v2");