| /* | |
| * Watchdog driver for the wm831x PMICs | |
| * | |
| * Copyright (C) 2009 Wolfson Microelectronics | |
| * | |
| * This program is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public License | |
| * as published by the Free Software Foundation | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/moduleparam.h> | |
| #include <linux/types.h> | |
| #include <linux/kernel.h> | |
| #include <linux/slab.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/watchdog.h> | |
| #include <linux/uaccess.h> | |
| #include <linux/gpio.h> | |
| #include <linux/mfd/wm831x/core.h> | |
| #include <linux/mfd/wm831x/pdata.h> | |
| #include <linux/mfd/wm831x/watchdog.h> | |
| static bool nowayout = WATCHDOG_NOWAYOUT; | |
| module_param(nowayout, bool, 0); | |
| MODULE_PARM_DESC(nowayout, | |
| "Watchdog cannot be stopped once started (default=" | |
| __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
| struct wm831x_wdt_drvdata { | |
| struct watchdog_device wdt; | |
| struct wm831x *wm831x; | |
| struct mutex lock; | |
| int update_gpio; | |
| int update_state; | |
| }; | |
| /* We can't use the sub-second values here but they're included | |
| * for completeness. */ | |
| static struct { | |
| unsigned int time; /* Seconds */ | |
| u16 val; /* WDOG_TO value */ | |
| } wm831x_wdt_cfgs[] = { | |
| { 1, 2 }, | |
| { 2, 3 }, | |
| { 4, 4 }, | |
| { 8, 5 }, | |
| { 16, 6 }, | |
| { 32, 7 }, | |
| { 33, 7 }, /* Actually 32.768s so include both, others round down */ | |
| }; | |
| static int wm831x_wdt_start(struct watchdog_device *wdt_dev) | |
| { | |
| struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); | |
| struct wm831x *wm831x = driver_data->wm831x; | |
| int ret; | |
| mutex_lock(&driver_data->lock); | |
| ret = wm831x_reg_unlock(wm831x); | |
| if (ret == 0) { | |
| ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, | |
| WM831X_WDOG_ENA, WM831X_WDOG_ENA); | |
| wm831x_reg_lock(wm831x); | |
| } else { | |
| dev_err(wm831x->dev, "Failed to unlock security key: %d\n", | |
| ret); | |
| } | |
| mutex_unlock(&driver_data->lock); | |
| return ret; | |
| } | |
| static int wm831x_wdt_stop(struct watchdog_device *wdt_dev) | |
| { | |
| struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); | |
| struct wm831x *wm831x = driver_data->wm831x; | |
| int ret; | |
| mutex_lock(&driver_data->lock); | |
| ret = wm831x_reg_unlock(wm831x); | |
| if (ret == 0) { | |
| ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, | |
| WM831X_WDOG_ENA, 0); | |
| wm831x_reg_lock(wm831x); | |
| } else { | |
| dev_err(wm831x->dev, "Failed to unlock security key: %d\n", | |
| ret); | |
| } | |
| mutex_unlock(&driver_data->lock); | |
| return ret; | |
| } | |
| static int wm831x_wdt_ping(struct watchdog_device *wdt_dev) | |
| { | |
| struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); | |
| struct wm831x *wm831x = driver_data->wm831x; | |
| int ret; | |
| u16 reg; | |
| mutex_lock(&driver_data->lock); | |
| if (driver_data->update_gpio) { | |
| gpio_set_value_cansleep(driver_data->update_gpio, | |
| driver_data->update_state); | |
| driver_data->update_state = !driver_data->update_state; | |
| ret = 0; | |
| goto out; | |
| } | |
| reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG); | |
| if (!(reg & WM831X_WDOG_RST_SRC)) { | |
| dev_err(wm831x->dev, "Hardware watchdog update unsupported\n"); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| reg |= WM831X_WDOG_RESET; | |
| ret = wm831x_reg_unlock(wm831x); | |
| if (ret == 0) { | |
| ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg); | |
| wm831x_reg_lock(wm831x); | |
| } else { | |
| dev_err(wm831x->dev, "Failed to unlock security key: %d\n", | |
| ret); | |
| } | |
| out: | |
| mutex_unlock(&driver_data->lock); | |
| return ret; | |
| } | |
| static int wm831x_wdt_set_timeout(struct watchdog_device *wdt_dev, | |
| unsigned int timeout) | |
| { | |
| struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); | |
| struct wm831x *wm831x = driver_data->wm831x; | |
| int ret, i; | |
| for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++) | |
| if (wm831x_wdt_cfgs[i].time == timeout) | |
| break; | |
| if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) | |
| return -EINVAL; | |
| ret = wm831x_reg_unlock(wm831x); | |
| if (ret == 0) { | |
| ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, | |
| WM831X_WDOG_TO_MASK, | |
| wm831x_wdt_cfgs[i].val); | |
| wm831x_reg_lock(wm831x); | |
| } else { | |
| dev_err(wm831x->dev, "Failed to unlock security key: %d\n", | |
| ret); | |
| } | |
| wdt_dev->timeout = timeout; | |
| return ret; | |
| } | |
| static const struct watchdog_info wm831x_wdt_info = { | |
| .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
| .identity = "WM831x Watchdog", | |
| }; | |
| static const struct watchdog_ops wm831x_wdt_ops = { | |
| .owner = THIS_MODULE, | |
| .start = wm831x_wdt_start, | |
| .stop = wm831x_wdt_stop, | |
| .ping = wm831x_wdt_ping, | |
| .set_timeout = wm831x_wdt_set_timeout, | |
| }; | |
| static int wm831x_wdt_probe(struct platform_device *pdev) | |
| { | |
| struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); | |
| struct wm831x_pdata *chip_pdata = dev_get_platdata(pdev->dev.parent); | |
| struct wm831x_watchdog_pdata *pdata; | |
| struct wm831x_wdt_drvdata *driver_data; | |
| struct watchdog_device *wm831x_wdt; | |
| int reg, ret, i; | |
| ret = wm831x_reg_read(wm831x, WM831X_WATCHDOG); | |
| if (ret < 0) { | |
| dev_err(wm831x->dev, "Failed to read watchdog status: %d\n", | |
| ret); | |
| goto err; | |
| } | |
| reg = ret; | |
| if (reg & WM831X_WDOG_DEBUG) | |
| dev_warn(wm831x->dev, "Watchdog is paused\n"); | |
| driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), | |
| GFP_KERNEL); | |
| if (!driver_data) { | |
| ret = -ENOMEM; | |
| goto err; | |
| } | |
| mutex_init(&driver_data->lock); | |
| driver_data->wm831x = wm831x; | |
| wm831x_wdt = &driver_data->wdt; | |
| wm831x_wdt->info = &wm831x_wdt_info; | |
| wm831x_wdt->ops = &wm831x_wdt_ops; | |
| wm831x_wdt->parent = &pdev->dev; | |
| watchdog_set_nowayout(wm831x_wdt, nowayout); | |
| watchdog_set_drvdata(wm831x_wdt, driver_data); | |
| reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG); | |
| reg &= WM831X_WDOG_TO_MASK; | |
| for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++) | |
| if (wm831x_wdt_cfgs[i].val == reg) | |
| break; | |
| if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) | |
| dev_warn(wm831x->dev, | |
| "Unknown watchdog timeout: %x\n", reg); | |
| else | |
| wm831x_wdt->timeout = wm831x_wdt_cfgs[i].time; | |
| /* Apply any configuration */ | |
| if (chip_pdata) | |
| pdata = chip_pdata->watchdog; | |
| else | |
| pdata = NULL; | |
| if (pdata) { | |
| reg &= ~(WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK | | |
| WM831X_WDOG_RST_SRC); | |
| reg |= pdata->primary << WM831X_WDOG_PRIMACT_SHIFT; | |
| reg |= pdata->secondary << WM831X_WDOG_SECACT_SHIFT; | |
| reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT; | |
| if (pdata->update_gpio) { | |
| ret = devm_gpio_request_one(&pdev->dev, | |
| pdata->update_gpio, | |
| GPIOF_OUT_INIT_LOW, | |
| "Watchdog update"); | |
| if (ret < 0) { | |
| dev_err(wm831x->dev, | |
| "Failed to request update GPIO: %d\n", | |
| ret); | |
| goto err; | |
| } | |
| driver_data->update_gpio = pdata->update_gpio; | |
| /* Make sure the watchdog takes hardware updates */ | |
| reg |= WM831X_WDOG_RST_SRC; | |
| } | |
| ret = wm831x_reg_unlock(wm831x); | |
| if (ret == 0) { | |
| ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg); | |
| wm831x_reg_lock(wm831x); | |
| } else { | |
| dev_err(wm831x->dev, | |
| "Failed to unlock security key: %d\n", ret); | |
| goto err; | |
| } | |
| } | |
| ret = watchdog_register_device(&driver_data->wdt); | |
| if (ret != 0) { | |
| dev_err(wm831x->dev, "watchdog_register_device() failed: %d\n", | |
| ret); | |
| goto err; | |
| } | |
| platform_set_drvdata(pdev, driver_data); | |
| return 0; | |
| err: | |
| return ret; | |
| } | |
| static int wm831x_wdt_remove(struct platform_device *pdev) | |
| { | |
| struct wm831x_wdt_drvdata *driver_data = platform_get_drvdata(pdev); | |
| watchdog_unregister_device(&driver_data->wdt); | |
| return 0; | |
| } | |
| static struct platform_driver wm831x_wdt_driver = { | |
| .probe = wm831x_wdt_probe, | |
| .remove = wm831x_wdt_remove, | |
| .driver = { | |
| .name = "wm831x-watchdog", | |
| }, | |
| }; | |
| module_platform_driver(wm831x_wdt_driver); | |
| MODULE_AUTHOR("Mark Brown"); | |
| MODULE_DESCRIPTION("WM831x Watchdog"); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_ALIAS("platform:wm831x-watchdog"); |