^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) * drivers/char/watchdog/ixp4xx_wdt.c
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) * Watchdog driver for Intel IXP4xx network processors
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) * Author: Deepak Saxena <dsaxena@plexity.net>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) * Copyright 2004 (c) MontaVista, Software, Inc.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) * This file is licensed under the terms of the GNU General Public
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) * License version 2. This program is licensed "as is" without any
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) * warranty of any kind, whether express or implied.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) #include <linux/module.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19) #include <linux/moduleparam.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) #include <linux/types.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) #include <linux/kernel.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) #include <linux/fs.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) #include <linux/miscdevice.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) #include <linux/of.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) #include <linux/watchdog.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) #include <linux/init.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) #include <linux/bitops.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) #include <linux/uaccess.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29) #include <mach/hardware.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31) static bool nowayout = WATCHDOG_NOWAYOUT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) static int heartbeat = 60; /* (secs) Default is 1 minute */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) static unsigned long wdt_status;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) static unsigned long boot_status;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) static DEFINE_SPINLOCK(wdt_lock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37) #define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) #define WDT_IN_USE 0
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) #define WDT_OK_TO_CLOSE 1
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) static void wdt_enable(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) spin_lock(&wdt_lock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) *IXP4XX_OSWK = IXP4XX_WDT_KEY;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) *IXP4XX_OSWE = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) *IXP4XX_OSWT = WDT_TICK_RATE * heartbeat;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) *IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49) *IXP4XX_OSWK = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) spin_unlock(&wdt_lock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) static void wdt_disable(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) spin_lock(&wdt_lock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) *IXP4XX_OSWK = IXP4XX_WDT_KEY;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) *IXP4XX_OSWE = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58) *IXP4XX_OSWK = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) spin_unlock(&wdt_lock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) static int ixp4xx_wdt_open(struct inode *inode, struct file *file)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64) if (test_and_set_bit(WDT_IN_USE, &wdt_status))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) return -EBUSY;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) wdt_enable();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) return stream_open(inode, file);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72) static ssize_t
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) if (len) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) if (!nowayout) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) size_t i;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79) clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81) for (i = 0; i != len; i++) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82) char c;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) if (get_user(c, data + i))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) return -EFAULT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86) if (c == 'V')
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) set_bit(WDT_OK_TO_CLOSE, &wdt_status);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) wdt_enable();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) return len;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95) static const struct watchdog_info ident = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) .identity = "IXP4xx Watchdog",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) static long ixp4xx_wdt_ioctl(struct file *file, unsigned int cmd,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) unsigned long arg)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) int ret = -ENOTTY;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) int time;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) switch (cmd) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) case WDIOC_GETSUPPORT:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) ret = copy_to_user((struct watchdog_info *)arg, &ident,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) sizeof(ident)) ? -EFAULT : 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) case WDIOC_GETSTATUS:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) ret = put_user(0, (int *)arg);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) case WDIOC_GETBOOTSTATUS:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) ret = put_user(boot_status, (int *)arg);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) case WDIOC_KEEPALIVE:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) wdt_enable();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) ret = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) case WDIOC_SETTIMEOUT:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) ret = get_user(time, (int *)arg);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) if (ret)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) if (time <= 0 || time > 60) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) ret = -EINVAL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) heartbeat = time;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) wdt_enable();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) fallthrough;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) case WDIOC_GETTIMEOUT:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) ret = put_user(heartbeat, (int *)arg);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) return ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) static int ixp4xx_wdt_release(struct inode *inode, struct file *file)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150) if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151) wdt_disable();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) else
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) pr_crit("Device closed unexpectedly - timer will not stop\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) clear_bit(WDT_IN_USE, &wdt_status);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) static const struct file_operations ixp4xx_wdt_fops = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162) .owner = THIS_MODULE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) .llseek = no_llseek,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) .write = ixp4xx_wdt_write,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) .unlocked_ioctl = ixp4xx_wdt_ioctl,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) .compat_ioctl = compat_ptr_ioctl,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167) .open = ixp4xx_wdt_open,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) .release = ixp4xx_wdt_release,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) static struct miscdevice ixp4xx_wdt_miscdev = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) .minor = WATCHDOG_MINOR,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) .name = "watchdog",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174) .fops = &ixp4xx_wdt_fops,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177) static int __init ixp4xx_wdt_init(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) int ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) * FIXME: we bail out on device tree boot but this really needs
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) * to be fixed in a nicer way: this registers the MDIO bus before
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184) * even matching the driver infrastructure, we should only probe
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185) * detected hardware.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187) if (of_have_populated_dt())
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) return -ENODEV;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189) if (!(read_cpuid_id() & 0xf) && !cpu_is_ixp46x()) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) pr_err("Rev. A0 IXP42x CPU detected - watchdog disabled\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192) return -ENODEV;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ?
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195) WDIOF_CARDRESET : 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) ret = misc_register(&ixp4xx_wdt_miscdev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197) if (ret == 0)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) pr_info("timer heartbeat %d sec\n", heartbeat);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199) return ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202) static void __exit ixp4xx_wdt_exit(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 204) misc_deregister(&ixp4xx_wdt_miscdev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 205) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 206)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 207)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 208) module_init(ixp4xx_wdt_init);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 209) module_exit(ixp4xx_wdt_exit);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 210)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 211) MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 212) MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 213)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 214) module_param(heartbeat, int, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 215) MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 216)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 217) module_param(nowayout, bool, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 218) MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 219)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 220) MODULE_LICENSE("GPL");