430 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * exar_wdt.c - Driver for the watchdog present in some4 * Exar/MaxLinear UART chips like the XR28V38x.5 *6 * (c) Copyright 2022 D. Müller <d.mueller@elsoft.ch>.7 *8 */9 10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt11 12#include <linux/io.h>13#include <linux/list.h>14#include <linux/module.h>15#include <linux/platform_device.h>16#include <linux/slab.h>17#include <linux/watchdog.h>18 19#define DRV_NAME "exar_wdt"20 21static const unsigned short sio_config_ports[] = { 0x2e, 0x4e };22static const unsigned char sio_enter_keys[] = { 0x67, 0x77, 0x87, 0xA0 };23#define EXAR_EXIT_KEY 0xAA24 25#define EXAR_LDN 0x0726#define EXAR_DID 0x2027#define EXAR_VID 0x2328#define EXAR_WDT 0x2629#define EXAR_ACT 0x3030#define EXAR_RTBASE 0x6031 32#define EXAR_WDT_LDEV 0x0833 34#define EXAR_VEN_ID 0x13A835#define EXAR_DEV_382 0x038236#define EXAR_DEV_384 0x038437 38/* WDT runtime registers */39#define WDT_CTRL 0x0040#define WDT_VAL 0x0141 42#define WDT_UNITS_10MS 0x0 /* the 10 millisec unit of the HW is not used */43#define WDT_UNITS_SEC 0x244#define WDT_UNITS_MIN 0x445 46/* default WDT control for WDTOUT signal activ / rearm by read */47#define EXAR_WDT_DEF_CONF 048 49struct wdt_pdev_node {50 struct list_head list;51 struct platform_device *pdev;52 const char name[16];53};54 55struct wdt_priv {56 /* the lock for WDT io operations */57 spinlock_t io_lock;58 struct resource wdt_res;59 struct watchdog_device wdt_dev;60 unsigned short did;61 unsigned short config_port;62 unsigned char enter_key;63 unsigned char unit;64 unsigned char timeout;65};66 67#define WATCHDOG_TIMEOUT 6068 69static int timeout = WATCHDOG_TIMEOUT;70module_param(timeout, int, 0);71MODULE_PARM_DESC(timeout,72 "Watchdog timeout in seconds. 1<=timeout<=15300, default="73 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");74 75static bool nowayout = WATCHDOG_NOWAYOUT;76module_param(nowayout, bool, 0);77MODULE_PARM_DESC(nowayout,78 "Watchdog cannot be stopped once started (default="79 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");80 81static int exar_sio_enter(const unsigned short config_port,82 const unsigned char key)83{84 if (!request_muxed_region(config_port, 2, DRV_NAME))85 return -EBUSY;86 87 /* write the ENTER-KEY twice */88 outb(key, config_port);89 outb(key, config_port);90 91 return 0;92}93 94static void exar_sio_exit(const unsigned short config_port)95{96 outb(EXAR_EXIT_KEY, config_port);97 release_region(config_port, 2);98}99 100static unsigned char exar_sio_read(const unsigned short config_port,101 const unsigned char reg)102{103 outb(reg, config_port);104 return inb(config_port + 1);105}106 107static void exar_sio_write(const unsigned short config_port,108 const unsigned char reg, const unsigned char val)109{110 outb(reg, config_port);111 outb(val, config_port + 1);112}113 114static unsigned short exar_sio_read16(const unsigned short config_port,115 const unsigned char reg)116{117 unsigned char msb, lsb;118 119 msb = exar_sio_read(config_port, reg);120 lsb = exar_sio_read(config_port, reg + 1);121 122 return (msb << 8) | lsb;123}124 125static void exar_sio_select_wdt(const unsigned short config_port)126{127 exar_sio_write(config_port, EXAR_LDN, EXAR_WDT_LDEV);128}129 130static void exar_wdt_arm(const struct wdt_priv *priv)131{132 unsigned short rt_base = priv->wdt_res.start;133 134 /* write timeout value twice to arm watchdog */135 outb(priv->timeout, rt_base + WDT_VAL);136 outb(priv->timeout, rt_base + WDT_VAL);137}138 139static void exar_wdt_disarm(const struct wdt_priv *priv)140{141 unsigned short rt_base = priv->wdt_res.start;142 143 /*144 * use two accesses with different values to make sure145 * that a combination of a previous single access and146 * the ones below with the same value are not falsely147 * interpreted as "arm watchdog"148 */149 outb(0xFF, rt_base + WDT_VAL);150 outb(0, rt_base + WDT_VAL);151}152 153static int exar_wdt_start(struct watchdog_device *wdog)154{155 struct wdt_priv *priv = watchdog_get_drvdata(wdog);156 unsigned short rt_base = priv->wdt_res.start;157 158 spin_lock(&priv->io_lock);159 160 exar_wdt_disarm(priv);161 outb(priv->unit, rt_base + WDT_CTRL);162 exar_wdt_arm(priv);163 164 spin_unlock(&priv->io_lock);165 return 0;166}167 168static int exar_wdt_stop(struct watchdog_device *wdog)169{170 struct wdt_priv *priv = watchdog_get_drvdata(wdog);171 172 spin_lock(&priv->io_lock);173 174 exar_wdt_disarm(priv);175 176 spin_unlock(&priv->io_lock);177 return 0;178}179 180static int exar_wdt_keepalive(struct watchdog_device *wdog)181{182 struct wdt_priv *priv = watchdog_get_drvdata(wdog);183 unsigned short rt_base = priv->wdt_res.start;184 185 spin_lock(&priv->io_lock);186 187 /* reading the WDT_VAL reg will feed the watchdog */188 inb(rt_base + WDT_VAL);189 190 spin_unlock(&priv->io_lock);191 return 0;192}193 194static int exar_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)195{196 struct wdt_priv *priv = watchdog_get_drvdata(wdog);197 bool unit_min = false;198 199 /*200 * if new timeout is bigger then 255 seconds, change the201 * unit to minutes and round the timeout up to the next whole minute202 */203 if (t > 255) {204 unit_min = true;205 t = DIV_ROUND_UP(t, 60);206 }207 208 /* save for later use in exar_wdt_start() */209 priv->unit = unit_min ? WDT_UNITS_MIN : WDT_UNITS_SEC;210 priv->timeout = t;211 212 wdog->timeout = unit_min ? t * 60 : t;213 214 if (watchdog_hw_running(wdog))215 exar_wdt_start(wdog);216 217 return 0;218}219 220static const struct watchdog_info exar_wdt_info = {221 .options = WDIOF_KEEPALIVEPING |222 WDIOF_SETTIMEOUT |223 WDIOF_MAGICCLOSE,224 .identity = "Exar/MaxLinear XR28V38x Watchdog",225};226 227static const struct watchdog_ops exar_wdt_ops = {228 .owner = THIS_MODULE,229 .start = exar_wdt_start,230 .stop = exar_wdt_stop,231 .ping = exar_wdt_keepalive,232 .set_timeout = exar_wdt_set_timeout,233};234 235static int exar_wdt_config(struct watchdog_device *wdog,236 const unsigned char conf)237{238 struct wdt_priv *priv = watchdog_get_drvdata(wdog);239 int ret;240 241 ret = exar_sio_enter(priv->config_port, priv->enter_key);242 if (ret)243 return ret;244 245 exar_sio_select_wdt(priv->config_port);246 exar_sio_write(priv->config_port, EXAR_WDT, conf);247 248 exar_sio_exit(priv->config_port);249 250 return 0;251}252 253static int __init exar_wdt_probe(struct platform_device *pdev)254{255 struct device *dev = &pdev->dev;256 struct wdt_priv *priv = dev->platform_data;257 struct watchdog_device *wdt_dev = &priv->wdt_dev;258 struct resource *res;259 int ret;260 261 res = platform_get_resource(pdev, IORESOURCE_IO, 0);262 if (!res)263 return -ENXIO;264 265 spin_lock_init(&priv->io_lock);266 267 wdt_dev->info = &exar_wdt_info;268 wdt_dev->ops = &exar_wdt_ops;269 wdt_dev->min_timeout = 1;270 wdt_dev->max_timeout = 255 * 60;271 272 watchdog_init_timeout(wdt_dev, timeout, NULL);273 watchdog_set_nowayout(wdt_dev, nowayout);274 watchdog_stop_on_reboot(wdt_dev);275 watchdog_stop_on_unregister(wdt_dev);276 watchdog_set_drvdata(wdt_dev, priv);277 278 ret = exar_wdt_config(wdt_dev, EXAR_WDT_DEF_CONF);279 if (ret)280 return ret;281 282 exar_wdt_set_timeout(wdt_dev, timeout);283 /* Make sure that the watchdog is not running */284 exar_wdt_stop(wdt_dev);285 286 ret = devm_watchdog_register_device(dev, wdt_dev);287 if (ret)288 return ret;289 290 dev_info(dev, "XR28V%X WDT initialized. timeout=%d sec (nowayout=%d)\n",291 priv->did, timeout, nowayout);292 293 return 0;294}295 296static unsigned short __init exar_detect(const unsigned short config_port,297 const unsigned char key,298 unsigned short *rt_base)299{300 int ret;301 unsigned short base = 0;302 unsigned short vid, did;303 304 ret = exar_sio_enter(config_port, key);305 if (ret)306 return 0;307 308 vid = exar_sio_read16(config_port, EXAR_VID);309 did = exar_sio_read16(config_port, EXAR_DID);310 311 /* check for the vendor and device IDs we currently know about */312 if (vid == EXAR_VEN_ID &&313 (did == EXAR_DEV_382 ||314 did == EXAR_DEV_384)) {315 exar_sio_select_wdt(config_port);316 /* is device active? */317 if (exar_sio_read(config_port, EXAR_ACT) == 0x01)318 base = exar_sio_read16(config_port, EXAR_RTBASE);319 }320 321 exar_sio_exit(config_port);322 323 if (base) {324 pr_debug("Found a XR28V%X WDT (conf: 0x%x / rt: 0x%04x)\n",325 did, config_port, base);326 *rt_base = base;327 return did;328 }329 330 return 0;331}332 333static struct platform_driver exar_wdt_driver = {334 .driver = {335 .name = DRV_NAME,336 },337};338 339static LIST_HEAD(pdev_list);340 341static int __init exar_wdt_register(struct wdt_priv *priv, const int idx)342{343 struct wdt_pdev_node *n;344 345 n = kzalloc(sizeof(*n), GFP_KERNEL);346 if (!n)347 return -ENOMEM;348 349 INIT_LIST_HEAD(&n->list);350 351 scnprintf((char *)n->name, sizeof(n->name), DRV_NAME ".%d", idx);352 priv->wdt_res.name = n->name;353 354 n->pdev = platform_device_register_resndata(NULL, DRV_NAME, idx,355 &priv->wdt_res, 1,356 priv, sizeof(*priv));357 if (IS_ERR(n->pdev)) {358 int err = PTR_ERR(n->pdev);359 360 kfree(n);361 return err;362 }363 364 list_add_tail(&n->list, &pdev_list);365 366 return 0;367}368 369static void exar_wdt_unregister(void)370{371 struct wdt_pdev_node *n, *t;372 373 list_for_each_entry_safe(n, t, &pdev_list, list) {374 platform_device_unregister(n->pdev);375 list_del(&n->list);376 kfree(n);377 }378}379 380static int __init exar_wdt_init(void)381{382 int ret, i, j, idx = 0;383 384 /* search for active Exar watchdogs on all possible locations */385 for (i = 0; i < ARRAY_SIZE(sio_config_ports); i++) {386 for (j = 0; j < ARRAY_SIZE(sio_enter_keys); j++) {387 unsigned short did, rt_base = 0;388 389 did = exar_detect(sio_config_ports[i],390 sio_enter_keys[j],391 &rt_base);392 393 if (did) {394 struct wdt_priv priv = {395 .wdt_res = DEFINE_RES_IO(rt_base, 2),396 .did = did,397 .config_port = sio_config_ports[i],398 .enter_key = sio_enter_keys[j],399 };400 401 ret = exar_wdt_register(&priv, idx);402 if (!ret)403 idx++;404 }405 }406 }407 408 if (!idx)409 return -ENODEV;410 411 ret = platform_driver_probe(&exar_wdt_driver, exar_wdt_probe);412 if (ret)413 exar_wdt_unregister();414 415 return ret;416}417 418static void __exit exar_wdt_exit(void)419{420 exar_wdt_unregister();421 platform_driver_unregister(&exar_wdt_driver);422}423 424module_init(exar_wdt_init);425module_exit(exar_wdt_exit);426 427MODULE_AUTHOR("David Müller <d.mueller@elsoft.ch>");428MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver");429MODULE_LICENSE("GPL");430