1/* 2 * Watchdog driver for the A21 VME CPU Boards 3 * 4 * Copyright (C) 2013 MEN Mikro Elektronik Nuernberg GmbH 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation 9 */ 10#include <linux/module.h> 11#include <linux/moduleparam.h> 12#include <linux/types.h> 13#include <linux/kernel.h> 14#include <linux/slab.h> 15#include <linux/platform_device.h> 16#include <linux/watchdog.h> 17#include <linux/uaccess.h> 18#include <linux/gpio.h> 19#include <linux/of_gpio.h> 20#include <linux/delay.h> 21#include <linux/bitops.h> 22 23#define NUM_GPIOS 6 24 25enum a21_wdt_gpios { 26 GPIO_WD_ENAB, 27 GPIO_WD_FAST, 28 GPIO_WD_TRIG, 29 GPIO_WD_RST0, 30 GPIO_WD_RST1, 31 GPIO_WD_RST2, 32}; 33 34struct a21_wdt_drv { 35 struct watchdog_device wdt; 36 struct mutex lock; 37 unsigned gpios[NUM_GPIOS]; 38}; 39 40static bool nowayout = WATCHDOG_NOWAYOUT; 41module_param(nowayout, bool, 0); 42MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 43 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 44 45static unsigned int a21_wdt_get_bootstatus(struct a21_wdt_drv *drv) 46{ 47 int reset = 0; 48 49 reset |= gpio_get_value(drv->gpios[GPIO_WD_RST0]) ? (1 << 0) : 0; 50 reset |= gpio_get_value(drv->gpios[GPIO_WD_RST1]) ? (1 << 1) : 0; 51 reset |= gpio_get_value(drv->gpios[GPIO_WD_RST2]) ? (1 << 2) : 0; 52 53 return reset; 54} 55 56static int a21_wdt_start(struct watchdog_device *wdt) 57{ 58 struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); 59 60 mutex_lock(&drv->lock); 61 62 gpio_set_value(drv->gpios[GPIO_WD_ENAB], 1); 63 64 mutex_unlock(&drv->lock); 65 66 return 0; 67} 68 69static int a21_wdt_stop(struct watchdog_device *wdt) 70{ 71 struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); 72 73 mutex_lock(&drv->lock); 74 75 gpio_set_value(drv->gpios[GPIO_WD_ENAB], 0); 76 77 mutex_unlock(&drv->lock); 78 79 return 0; 80} 81 82static int a21_wdt_ping(struct watchdog_device *wdt) 83{ 84 struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); 85 86 mutex_lock(&drv->lock); 87 88 gpio_set_value(drv->gpios[GPIO_WD_TRIG], 0); 89 ndelay(10); 90 gpio_set_value(drv->gpios[GPIO_WD_TRIG], 1); 91 92 mutex_unlock(&drv->lock); 93 94 return 0; 95} 96 97static int a21_wdt_set_timeout(struct watchdog_device *wdt, 98 unsigned int timeout) 99{ 100 struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); 101 102 if (timeout != 1 && timeout != 30) { 103 dev_err(wdt->dev, "Only 1 and 30 allowed as timeout\n"); 104 return -EINVAL; 105 } 106 107 if (timeout == 30 && wdt->timeout == 1) { 108 dev_err(wdt->dev, 109 "Transition from fast to slow mode not allowed\n"); 110 return -EINVAL; 111 } 112 113 mutex_lock(&drv->lock); 114 115 if (timeout == 1) 116 gpio_set_value(drv->gpios[GPIO_WD_FAST], 1); 117 else 118 gpio_set_value(drv->gpios[GPIO_WD_FAST], 0); 119 120 wdt->timeout = timeout; 121 122 mutex_unlock(&drv->lock); 123 124 return 0; 125} 126 127static const struct watchdog_info a21_wdt_info = { 128 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 129 .identity = "MEN A21 Watchdog", 130}; 131 132static const struct watchdog_ops a21_wdt_ops = { 133 .owner = THIS_MODULE, 134 .start = a21_wdt_start, 135 .stop = a21_wdt_stop, 136 .ping = a21_wdt_ping, 137 .set_timeout = a21_wdt_set_timeout, 138}; 139 140static struct watchdog_device a21_wdt = { 141 .info = &a21_wdt_info, 142 .ops = &a21_wdt_ops, 143 .min_timeout = 1, 144 .max_timeout = 30, 145}; 146 147static int a21_wdt_probe(struct platform_device *pdev) 148{ 149 struct device_node *node; 150 struct a21_wdt_drv *drv; 151 unsigned int reset = 0; 152 int num_gpios; 153 int ret; 154 int i; 155 156 drv = devm_kzalloc(&pdev->dev, sizeof(struct a21_wdt_drv), GFP_KERNEL); 157 if (!drv) 158 return -ENOMEM; 159 160 /* Fill GPIO pin array */ 161 node = pdev->dev.of_node; 162 163 num_gpios = of_gpio_count(node); 164 if (num_gpios != NUM_GPIOS) { 165 dev_err(&pdev->dev, "gpios DT property wrong, got %d want %d", 166 num_gpios, NUM_GPIOS); 167 return -ENODEV; 168 } 169 170 for (i = 0; i < num_gpios; i++) { 171 int val; 172 173 val = of_get_gpio(node, i); 174 if (val < 0) 175 return val; 176 177 drv->gpios[i] = val; 178 } 179 180 /* Request the used GPIOs */ 181 for (i = 0; i < num_gpios; i++) { 182 ret = devm_gpio_request(&pdev->dev, drv->gpios[i], 183 "MEN A21 Watchdog"); 184 if (ret) 185 return ret; 186 187 if (i < GPIO_WD_RST0) 188 ret = gpio_direction_output(drv->gpios[i], 189 gpio_get_value(drv->gpios[i])); 190 else /* GPIO_WD_RST[0..2] are inputs */ 191 ret = gpio_direction_input(drv->gpios[i]); 192 if (ret) 193 return ret; 194 } 195 196 mutex_init(&drv->lock); 197 watchdog_init_timeout(&a21_wdt, 30, &pdev->dev); 198 watchdog_set_nowayout(&a21_wdt, nowayout); 199 watchdog_set_drvdata(&a21_wdt, drv); 200 201 reset = a21_wdt_get_bootstatus(drv); 202 if (reset == 2) 203 a21_wdt.bootstatus |= WDIOF_EXTERN1; 204 else if (reset == 4) 205 a21_wdt.bootstatus |= WDIOF_CARDRESET; 206 else if (reset == 5) 207 a21_wdt.bootstatus |= WDIOF_POWERUNDER; 208 else if (reset == 7) 209 a21_wdt.bootstatus |= WDIOF_EXTERN2; 210 211 ret = watchdog_register_device(&a21_wdt); 212 if (ret) { 213 dev_err(&pdev->dev, "Cannot register watchdog device\n"); 214 goto err_register_wd; 215 } 216 217 dev_set_drvdata(&pdev->dev, drv); 218 219 dev_info(&pdev->dev, "MEN A21 watchdog timer driver enabled\n"); 220 221 return 0; 222 223err_register_wd: 224 mutex_destroy(&drv->lock); 225 226 return ret; 227} 228 229static int a21_wdt_remove(struct platform_device *pdev) 230{ 231 struct a21_wdt_drv *drv = dev_get_drvdata(&pdev->dev); 232 233 dev_warn(&pdev->dev, 234 "Unregistering A21 watchdog driver, board may reboot\n"); 235 236 watchdog_unregister_device(&drv->wdt); 237 238 mutex_destroy(&drv->lock); 239 240 return 0; 241} 242 243static void a21_wdt_shutdown(struct platform_device *pdev) 244{ 245 struct a21_wdt_drv *drv = dev_get_drvdata(&pdev->dev); 246 247 gpio_set_value(drv->gpios[GPIO_WD_ENAB], 0); 248} 249 250static const struct of_device_id a21_wdt_ids[] = { 251 { .compatible = "men,a021-wdt" }, 252 { }, 253}; 254 255static struct platform_driver a21_wdt_driver = { 256 .probe = a21_wdt_probe, 257 .remove = a21_wdt_remove, 258 .shutdown = a21_wdt_shutdown, 259 .driver = { 260 .name = "a21-watchdog", 261 .of_match_table = a21_wdt_ids, 262 }, 263}; 264 265module_platform_driver(a21_wdt_driver); 266 267MODULE_AUTHOR("MEN Mikro Elektronik"); 268MODULE_DESCRIPTION("MEN A21 Watchdog"); 269MODULE_LICENSE("GPL"); 270MODULE_ALIAS("platform:a21-watchdog"); 271