Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  *      Intel Atom E6xx Watchdog driver
0004  *
0005  *      Copyright (C) 2011 Alexander Stein
0006  *                <alexander.stein@systec-electronic.com>
0007  */
0008 
0009 #include <linux/module.h>
0010 #include <linux/moduleparam.h>
0011 #include <linux/platform_device.h>
0012 #include <linux/io.h>
0013 #include <linux/kernel.h>
0014 #include <linux/types.h>
0015 #include <linux/watchdog.h>
0016 #include <linux/seq_file.h>
0017 #include <linux/debugfs.h>
0018 #include <linux/uaccess.h>
0019 #include <linux/spinlock.h>
0020 
0021 #define DRIVER_NAME "ie6xx_wdt"
0022 
0023 #define PV1 0x00
0024 #define PV2 0x04
0025 
0026 #define RR0 0x0c
0027 #define RR1 0x0d
0028 #define WDT_RELOAD  0x01
0029 #define WDT_TOUT    0x02
0030 
0031 #define WDTCR   0x10
0032 #define WDT_PRE_SEL 0x04
0033 #define WDT_RESET_SEL   0x08
0034 #define WDT_RESET_EN    0x10
0035 #define WDT_TOUT_EN 0x20
0036 
0037 #define DCR 0x14
0038 
0039 #define WDTLR   0x18
0040 #define WDT_LOCK    0x01
0041 #define WDT_ENABLE  0x02
0042 #define WDT_TOUT_CNF    0x03
0043 
0044 #define MIN_TIME    1
0045 #define MAX_TIME    (10 * 60) /* 10 minutes */
0046 #define DEFAULT_TIME    60
0047 
0048 static unsigned int timeout = DEFAULT_TIME;
0049 module_param(timeout, uint, 0);
0050 MODULE_PARM_DESC(timeout,
0051         "Default Watchdog timer setting ("
0052         __MODULE_STRING(DEFAULT_TIME) "s)."
0053         "The range is from 1 to 600");
0054 
0055 static bool nowayout = WATCHDOG_NOWAYOUT;
0056 module_param(nowayout, bool, 0);
0057 MODULE_PARM_DESC(nowayout,
0058     "Watchdog cannot be stopped once started (default="
0059         __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0060 
0061 static u8 resetmode = 0x10;
0062 module_param(resetmode, byte, 0);
0063 MODULE_PARM_DESC(resetmode,
0064     "Resetmode bits: 0x08 warm reset (cold reset otherwise), "
0065     "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)");
0066 
0067 static struct {
0068     unsigned short sch_wdtba;
0069     spinlock_t unlock_sequence;
0070 #ifdef CONFIG_DEBUG_FS
0071     struct dentry *debugfs;
0072 #endif
0073 } ie6xx_wdt_data;
0074 
0075 /*
0076  * This is needed to write to preload and reload registers
0077  * struct ie6xx_wdt_data.unlock_sequence must be used
0078  * to prevent sequence interrupts
0079  */
0080 static void ie6xx_wdt_unlock_registers(void)
0081 {
0082     outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0);
0083     outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0);
0084 }
0085 
0086 static int ie6xx_wdt_ping(struct watchdog_device *wdd)
0087 {
0088     spin_lock(&ie6xx_wdt_data.unlock_sequence);
0089     ie6xx_wdt_unlock_registers();
0090     outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1);
0091     spin_unlock(&ie6xx_wdt_data.unlock_sequence);
0092     return 0;
0093 }
0094 
0095 static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
0096 {
0097     u32 preload;
0098     u64 clock;
0099     u8 wdtcr;
0100 
0101     /* Watchdog clock is PCI Clock (33MHz) */
0102     clock = 33000000;
0103     /* and the preload value is loaded into [34:15] of the down counter */
0104     preload = (t * clock) >> 15;
0105     /*
0106      * Manual states preload must be one less.
0107      * Does not wrap as t is at least 1
0108      */
0109     preload -= 1;
0110 
0111     spin_lock(&ie6xx_wdt_data.unlock_sequence);
0112 
0113     /* Set ResetMode & Enable prescaler for range 10ms to 10 min */
0114     wdtcr = resetmode & 0x38;
0115     outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR);
0116 
0117     ie6xx_wdt_unlock_registers();
0118     outl(0, ie6xx_wdt_data.sch_wdtba + PV1);
0119 
0120     ie6xx_wdt_unlock_registers();
0121     outl(preload, ie6xx_wdt_data.sch_wdtba + PV2);
0122 
0123     ie6xx_wdt_unlock_registers();
0124     outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1);
0125 
0126     spin_unlock(&ie6xx_wdt_data.unlock_sequence);
0127 
0128     wdd->timeout = t;
0129     return 0;
0130 }
0131 
0132 static int ie6xx_wdt_start(struct watchdog_device *wdd)
0133 {
0134     ie6xx_wdt_set_timeout(wdd, wdd->timeout);
0135 
0136     /* Enable the watchdog timer */
0137     spin_lock(&ie6xx_wdt_data.unlock_sequence);
0138     outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR);
0139     spin_unlock(&ie6xx_wdt_data.unlock_sequence);
0140 
0141     return 0;
0142 }
0143 
0144 static int ie6xx_wdt_stop(struct watchdog_device *wdd)
0145 {
0146     if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK)
0147         return -1;
0148 
0149     /* Disable the watchdog timer */
0150     spin_lock(&ie6xx_wdt_data.unlock_sequence);
0151     outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR);
0152     spin_unlock(&ie6xx_wdt_data.unlock_sequence);
0153 
0154     return 0;
0155 }
0156 
0157 static const struct watchdog_info ie6xx_wdt_info = {
0158     .identity = "Intel Atom E6xx Watchdog",
0159     .options =  WDIOF_SETTIMEOUT |
0160             WDIOF_MAGICCLOSE |
0161             WDIOF_KEEPALIVEPING,
0162 };
0163 
0164 static const struct watchdog_ops ie6xx_wdt_ops = {
0165     .owner =    THIS_MODULE,
0166     .start =    ie6xx_wdt_start,
0167     .stop =     ie6xx_wdt_stop,
0168     .ping =     ie6xx_wdt_ping,
0169     .set_timeout =  ie6xx_wdt_set_timeout,
0170 };
0171 
0172 static struct watchdog_device ie6xx_wdt_dev = {
0173     .info =     &ie6xx_wdt_info,
0174     .ops =      &ie6xx_wdt_ops,
0175     .min_timeout =  MIN_TIME,
0176     .max_timeout =  MAX_TIME,
0177 };
0178 
0179 #ifdef CONFIG_DEBUG_FS
0180 
0181 static int ie6xx_wdt_show(struct seq_file *s, void *unused)
0182 {
0183     seq_printf(s, "PV1   = 0x%08x\n",
0184         inl(ie6xx_wdt_data.sch_wdtba + PV1));
0185     seq_printf(s, "PV2   = 0x%08x\n",
0186         inl(ie6xx_wdt_data.sch_wdtba + PV2));
0187     seq_printf(s, "RR    = 0x%08x\n",
0188         inw(ie6xx_wdt_data.sch_wdtba + RR0));
0189     seq_printf(s, "WDTCR = 0x%08x\n",
0190         inw(ie6xx_wdt_data.sch_wdtba + WDTCR));
0191     seq_printf(s, "DCR   = 0x%08x\n",
0192         inl(ie6xx_wdt_data.sch_wdtba + DCR));
0193     seq_printf(s, "WDTLR = 0x%08x\n",
0194         inw(ie6xx_wdt_data.sch_wdtba + WDTLR));
0195 
0196     seq_printf(s, "\n");
0197     return 0;
0198 }
0199 
0200 DEFINE_SHOW_ATTRIBUTE(ie6xx_wdt);
0201 
0202 static void ie6xx_wdt_debugfs_init(void)
0203 {
0204     /* /sys/kernel/debug/ie6xx_wdt */
0205     ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt",
0206         S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_fops);
0207 }
0208 
0209 static void ie6xx_wdt_debugfs_exit(void)
0210 {
0211     debugfs_remove(ie6xx_wdt_data.debugfs);
0212 }
0213 
0214 #else
0215 static void ie6xx_wdt_debugfs_init(void)
0216 {
0217 }
0218 
0219 static void ie6xx_wdt_debugfs_exit(void)
0220 {
0221 }
0222 #endif
0223 
0224 static int ie6xx_wdt_probe(struct platform_device *pdev)
0225 {
0226     struct resource *res;
0227     u8 wdtlr;
0228     int ret;
0229 
0230     res = platform_get_resource(pdev, IORESOURCE_IO, 0);
0231     if (!res)
0232         return -ENODEV;
0233 
0234     if (!request_region(res->start, resource_size(res), pdev->name)) {
0235         dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n",
0236             (u64)res->start);
0237         return -EBUSY;
0238     }
0239 
0240     ie6xx_wdt_data.sch_wdtba = res->start;
0241     dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba);
0242 
0243     ie6xx_wdt_dev.timeout = timeout;
0244     watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout);
0245     ie6xx_wdt_dev.parent = &pdev->dev;
0246 
0247     spin_lock_init(&ie6xx_wdt_data.unlock_sequence);
0248 
0249     wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR);
0250     if (wdtlr & WDT_LOCK)
0251         dev_warn(&pdev->dev,
0252             "Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr);
0253 
0254     ie6xx_wdt_debugfs_init();
0255 
0256     ret = watchdog_register_device(&ie6xx_wdt_dev);
0257     if (ret)
0258         goto misc_register_error;
0259 
0260     return 0;
0261 
0262 misc_register_error:
0263     ie6xx_wdt_debugfs_exit();
0264     release_region(res->start, resource_size(res));
0265     ie6xx_wdt_data.sch_wdtba = 0;
0266     return ret;
0267 }
0268 
0269 static int ie6xx_wdt_remove(struct platform_device *pdev)
0270 {
0271     struct resource *res;
0272 
0273     res = platform_get_resource(pdev, IORESOURCE_IO, 0);
0274     ie6xx_wdt_stop(NULL);
0275     watchdog_unregister_device(&ie6xx_wdt_dev);
0276     ie6xx_wdt_debugfs_exit();
0277     release_region(res->start, resource_size(res));
0278     ie6xx_wdt_data.sch_wdtba = 0;
0279 
0280     return 0;
0281 }
0282 
0283 static struct platform_driver ie6xx_wdt_driver = {
0284     .probe      = ie6xx_wdt_probe,
0285     .remove     = ie6xx_wdt_remove,
0286     .driver     = {
0287         .name   = DRIVER_NAME,
0288     },
0289 };
0290 
0291 static int __init ie6xx_wdt_init(void)
0292 {
0293     /* Check boot parameters to verify that their initial values */
0294     /* are in range. */
0295     if ((timeout < MIN_TIME) ||
0296         (timeout > MAX_TIME)) {
0297         pr_err("Watchdog timer: value of timeout %d (dec) "
0298           "is out of range from %d to %d (dec)\n",
0299           timeout, MIN_TIME, MAX_TIME);
0300         return -EINVAL;
0301     }
0302 
0303     return platform_driver_register(&ie6xx_wdt_driver);
0304 }
0305 
0306 static void __exit ie6xx_wdt_exit(void)
0307 {
0308     platform_driver_unregister(&ie6xx_wdt_driver);
0309 }
0310 
0311 late_initcall(ie6xx_wdt_init);
0312 module_exit(ie6xx_wdt_exit);
0313 
0314 MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>");
0315 MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver");
0316 MODULE_LICENSE("GPL");
0317 MODULE_ALIAS("platform:" DRIVER_NAME);