0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0018
0019
0020 #include <linux/module.h> /* For module specific items */
0021 #include <linux/moduleparam.h> /* For new moduleparam's */
0022 #include <linux/types.h> /* For standard types (like size_t) */
0023 #include <linux/errno.h> /* For the -ENODEV/... values */
0024 #include <linux/kernel.h> /* For printk/... */
0025 #include <linux/miscdevice.h> /* For struct miscdevice */
0026 #include <linux/watchdog.h> /* For the watchdog specific items */
0027 #include <linux/init.h> /* For __init/__exit/... */
0028 #include <linux/fs.h> /* For file operations */
0029 #include <linux/platform_device.h> /* For platform_driver framework */
0030 #include <linux/ioport.h> /* For io-port access */
0031 #include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
0032 #include <linux/uaccess.h> /* For copy_to_user/put_user/... */
0033 #include <linux/io.h> /* For inb/outb/... */
0034
0035
0036 #define DRV_NAME "sch311x_wdt"
0037
0038
0039 #define GP60 0x47
0040 #define WDT_TIME_OUT 0x65
0041 #define WDT_VAL 0x66
0042 #define WDT_CFG 0x67
0043 #define WDT_CTRL 0x68
0044
0045
0046 static unsigned long sch311x_wdt_is_open;
0047 static char sch311x_wdt_expect_close;
0048 static struct platform_device *sch311x_wdt_pdev;
0049
0050 static int sch311x_ioports[] = { 0x2e, 0x4e, 0x162e, 0x164e, 0x00 };
0051
0052 static struct {
0053
0054 unsigned short runtime_reg;
0055
0056 int boot_status;
0057
0058 spinlock_t io_lock;
0059 } sch311x_wdt_data;
0060
0061
0062 static unsigned short force_id;
0063 module_param(force_id, ushort, 0);
0064 MODULE_PARM_DESC(force_id, "Override the detected device ID");
0065
0066 #define WATCHDOG_TIMEOUT 60
0067 static int timeout = WATCHDOG_TIMEOUT;
0068 module_param(timeout, int, 0);
0069 MODULE_PARM_DESC(timeout,
0070 "Watchdog timeout in seconds. 1<= timeout <=15300, default="
0071 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
0072
0073 static bool nowayout = WATCHDOG_NOWAYOUT;
0074 module_param(nowayout, bool, 0);
0075 MODULE_PARM_DESC(nowayout,
0076 "Watchdog cannot be stopped once started (default="
0077 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0078
0079
0080
0081
0082
0083 static inline void sch311x_sio_enter(int sio_config_port)
0084 {
0085 outb(0x55, sio_config_port);
0086 }
0087
0088 static inline void sch311x_sio_exit(int sio_config_port)
0089 {
0090 outb(0xaa, sio_config_port);
0091 }
0092
0093 static inline int sch311x_sio_inb(int sio_config_port, int reg)
0094 {
0095 outb(reg, sio_config_port);
0096 return inb(sio_config_port + 1);
0097 }
0098
0099 static inline void sch311x_sio_outb(int sio_config_port, int reg, int val)
0100 {
0101 outb(reg, sio_config_port);
0102 outb(val, sio_config_port + 1);
0103 }
0104
0105
0106
0107
0108
0109 static void sch311x_wdt_set_timeout(int t)
0110 {
0111 unsigned char timeout_unit = 0x80;
0112
0113
0114 if (t > 255) {
0115 timeout_unit = 0;
0116 t /= 60;
0117 }
0118
0119
0120
0121
0122
0123
0124 outb(timeout_unit, sch311x_wdt_data.runtime_reg + WDT_TIME_OUT);
0125
0126
0127
0128
0129 outb(t, sch311x_wdt_data.runtime_reg + WDT_VAL);
0130 }
0131
0132 static void sch311x_wdt_start(void)
0133 {
0134 unsigned char t;
0135
0136 spin_lock(&sch311x_wdt_data.io_lock);
0137
0138
0139 sch311x_wdt_set_timeout(timeout);
0140
0141
0142
0143
0144
0145
0146
0147
0148
0149 t = inb(sch311x_wdt_data.runtime_reg + GP60);
0150 outb((t & ~0x0d) | 0x0c, sch311x_wdt_data.runtime_reg + GP60);
0151
0152 spin_unlock(&sch311x_wdt_data.io_lock);
0153
0154 }
0155
0156 static void sch311x_wdt_stop(void)
0157 {
0158 unsigned char t;
0159
0160 spin_lock(&sch311x_wdt_data.io_lock);
0161
0162
0163 t = inb(sch311x_wdt_data.runtime_reg + GP60);
0164 outb((t & ~0x0d) | 0x01, sch311x_wdt_data.runtime_reg + GP60);
0165
0166 sch311x_wdt_set_timeout(0);
0167
0168 spin_unlock(&sch311x_wdt_data.io_lock);
0169 }
0170
0171 static void sch311x_wdt_keepalive(void)
0172 {
0173 spin_lock(&sch311x_wdt_data.io_lock);
0174 sch311x_wdt_set_timeout(timeout);
0175 spin_unlock(&sch311x_wdt_data.io_lock);
0176 }
0177
0178 static int sch311x_wdt_set_heartbeat(int t)
0179 {
0180 if (t < 1 || t > (255*60))
0181 return -EINVAL;
0182
0183
0184
0185 if (t > 255)
0186 t = (((t - 1) / 60) + 1) * 60;
0187
0188 timeout = t;
0189 return 0;
0190 }
0191
0192 static void sch311x_wdt_get_status(int *status)
0193 {
0194 unsigned char new_status;
0195
0196 *status = 0;
0197
0198 spin_lock(&sch311x_wdt_data.io_lock);
0199
0200
0201
0202
0203
0204
0205
0206
0207
0208
0209
0210 new_status = inb(sch311x_wdt_data.runtime_reg + WDT_CTRL);
0211 if (new_status & 0x01)
0212 *status |= WDIOF_CARDRESET;
0213
0214 spin_unlock(&sch311x_wdt_data.io_lock);
0215 }
0216
0217
0218
0219
0220
0221 static ssize_t sch311x_wdt_write(struct file *file, const char __user *buf,
0222 size_t count, loff_t *ppos)
0223 {
0224 if (count) {
0225 if (!nowayout) {
0226 size_t i;
0227
0228 sch311x_wdt_expect_close = 0;
0229
0230 for (i = 0; i != count; i++) {
0231 char c;
0232 if (get_user(c, buf + i))
0233 return -EFAULT;
0234 if (c == 'V')
0235 sch311x_wdt_expect_close = 42;
0236 }
0237 }
0238 sch311x_wdt_keepalive();
0239 }
0240 return count;
0241 }
0242
0243 static long sch311x_wdt_ioctl(struct file *file, unsigned int cmd,
0244 unsigned long arg)
0245 {
0246 int status;
0247 int new_timeout;
0248 void __user *argp = (void __user *)arg;
0249 int __user *p = argp;
0250 static const struct watchdog_info ident = {
0251 .options = WDIOF_KEEPALIVEPING |
0252 WDIOF_SETTIMEOUT |
0253 WDIOF_MAGICCLOSE,
0254 .firmware_version = 1,
0255 .identity = DRV_NAME,
0256 };
0257
0258 switch (cmd) {
0259 case WDIOC_GETSUPPORT:
0260 if (copy_to_user(argp, &ident, sizeof(ident)))
0261 return -EFAULT;
0262 break;
0263
0264 case WDIOC_GETSTATUS:
0265 {
0266 sch311x_wdt_get_status(&status);
0267 return put_user(status, p);
0268 }
0269 case WDIOC_GETBOOTSTATUS:
0270 return put_user(sch311x_wdt_data.boot_status, p);
0271
0272 case WDIOC_SETOPTIONS:
0273 {
0274 int options, retval = -EINVAL;
0275
0276 if (get_user(options, p))
0277 return -EFAULT;
0278 if (options & WDIOS_DISABLECARD) {
0279 sch311x_wdt_stop();
0280 retval = 0;
0281 }
0282 if (options & WDIOS_ENABLECARD) {
0283 sch311x_wdt_start();
0284 retval = 0;
0285 }
0286 return retval;
0287 }
0288 case WDIOC_KEEPALIVE:
0289 sch311x_wdt_keepalive();
0290 break;
0291
0292 case WDIOC_SETTIMEOUT:
0293 if (get_user(new_timeout, p))
0294 return -EFAULT;
0295 if (sch311x_wdt_set_heartbeat(new_timeout))
0296 return -EINVAL;
0297 sch311x_wdt_keepalive();
0298 fallthrough;
0299 case WDIOC_GETTIMEOUT:
0300 return put_user(timeout, p);
0301 default:
0302 return -ENOTTY;
0303 }
0304 return 0;
0305 }
0306
0307 static int sch311x_wdt_open(struct inode *inode, struct file *file)
0308 {
0309 if (test_and_set_bit(0, &sch311x_wdt_is_open))
0310 return -EBUSY;
0311
0312
0313
0314 sch311x_wdt_start();
0315 return stream_open(inode, file);
0316 }
0317
0318 static int sch311x_wdt_close(struct inode *inode, struct file *file)
0319 {
0320 if (sch311x_wdt_expect_close == 42) {
0321 sch311x_wdt_stop();
0322 } else {
0323 pr_crit("Unexpected close, not stopping watchdog!\n");
0324 sch311x_wdt_keepalive();
0325 }
0326 clear_bit(0, &sch311x_wdt_is_open);
0327 sch311x_wdt_expect_close = 0;
0328 return 0;
0329 }
0330
0331
0332
0333
0334
0335 static const struct file_operations sch311x_wdt_fops = {
0336 .owner = THIS_MODULE,
0337 .llseek = no_llseek,
0338 .write = sch311x_wdt_write,
0339 .unlocked_ioctl = sch311x_wdt_ioctl,
0340 .compat_ioctl = compat_ptr_ioctl,
0341 .open = sch311x_wdt_open,
0342 .release = sch311x_wdt_close,
0343 };
0344
0345 static struct miscdevice sch311x_wdt_miscdev = {
0346 .minor = WATCHDOG_MINOR,
0347 .name = "watchdog",
0348 .fops = &sch311x_wdt_fops,
0349 };
0350
0351
0352
0353
0354
0355 static int sch311x_wdt_probe(struct platform_device *pdev)
0356 {
0357 struct device *dev = &pdev->dev;
0358 int err;
0359
0360 spin_lock_init(&sch311x_wdt_data.io_lock);
0361
0362 if (!request_region(sch311x_wdt_data.runtime_reg + GP60, 1, DRV_NAME)) {
0363 dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n",
0364 sch311x_wdt_data.runtime_reg + GP60,
0365 sch311x_wdt_data.runtime_reg + GP60);
0366 err = -EBUSY;
0367 goto exit;
0368 }
0369
0370 if (!request_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4,
0371 DRV_NAME)) {
0372 dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n",
0373 sch311x_wdt_data.runtime_reg + WDT_TIME_OUT,
0374 sch311x_wdt_data.runtime_reg + WDT_CTRL);
0375 err = -EBUSY;
0376 goto exit_release_region;
0377 }
0378
0379
0380 sch311x_wdt_stop();
0381
0382
0383
0384
0385
0386
0387
0388
0389
0390
0391 outb(0, sch311x_wdt_data.runtime_reg + WDT_CFG);
0392
0393
0394
0395 if (sch311x_wdt_set_heartbeat(timeout)) {
0396 sch311x_wdt_set_heartbeat(WATCHDOG_TIMEOUT);
0397 dev_info(dev, "timeout value must be 1<=x<=15300, using %d\n",
0398 timeout);
0399 }
0400
0401
0402 sch311x_wdt_get_status(&sch311x_wdt_data.boot_status);
0403
0404 sch311x_wdt_miscdev.parent = dev;
0405
0406 err = misc_register(&sch311x_wdt_miscdev);
0407 if (err != 0) {
0408 dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n",
0409 WATCHDOG_MINOR, err);
0410 goto exit_release_region2;
0411 }
0412
0413 dev_info(dev,
0414 "SMSC SCH311x WDT initialized. timeout=%d sec (nowayout=%d)\n",
0415 timeout, nowayout);
0416
0417 return 0;
0418
0419 exit_release_region2:
0420 release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4);
0421 exit_release_region:
0422 release_region(sch311x_wdt_data.runtime_reg + GP60, 1);
0423 sch311x_wdt_data.runtime_reg = 0;
0424 exit:
0425 return err;
0426 }
0427
0428 static int sch311x_wdt_remove(struct platform_device *pdev)
0429 {
0430
0431 if (!nowayout)
0432 sch311x_wdt_stop();
0433
0434
0435 misc_deregister(&sch311x_wdt_miscdev);
0436 release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4);
0437 release_region(sch311x_wdt_data.runtime_reg + GP60, 1);
0438 sch311x_wdt_data.runtime_reg = 0;
0439 return 0;
0440 }
0441
0442 static void sch311x_wdt_shutdown(struct platform_device *dev)
0443 {
0444
0445 sch311x_wdt_stop();
0446 }
0447
0448 static struct platform_driver sch311x_wdt_driver = {
0449 .probe = sch311x_wdt_probe,
0450 .remove = sch311x_wdt_remove,
0451 .shutdown = sch311x_wdt_shutdown,
0452 .driver = {
0453 .name = DRV_NAME,
0454 },
0455 };
0456
0457 static int __init sch311x_detect(int sio_config_port, unsigned short *addr)
0458 {
0459 int err = 0, reg;
0460 unsigned short base_addr;
0461 unsigned char dev_id;
0462
0463 sch311x_sio_enter(sio_config_port);
0464
0465
0466
0467 reg = force_id ? force_id : sch311x_sio_inb(sio_config_port, 0x20);
0468 if (!(reg == 0x7c || reg == 0x7d || reg == 0x7f)) {
0469 err = -ENODEV;
0470 goto exit;
0471 }
0472 dev_id = reg == 0x7c ? 2 : reg == 0x7d ? 4 : 6;
0473
0474
0475 sch311x_sio_outb(sio_config_port, 0x07, 0x0a);
0476
0477
0478 if ((sch311x_sio_inb(sio_config_port, 0x30) & 0x01) == 0)
0479 pr_info("Seems that LDN 0x0a is not active...\n");
0480
0481
0482 base_addr = (sch311x_sio_inb(sio_config_port, 0x60) << 8) |
0483 sch311x_sio_inb(sio_config_port, 0x61);
0484 if (!base_addr) {
0485 pr_err("Base address not set\n");
0486 err = -ENODEV;
0487 goto exit;
0488 }
0489 *addr = base_addr;
0490
0491 pr_info("Found an SMSC SCH311%d chip at 0x%04x\n", dev_id, base_addr);
0492
0493 exit:
0494 sch311x_sio_exit(sio_config_port);
0495 return err;
0496 }
0497
0498 static int __init sch311x_wdt_init(void)
0499 {
0500 int err, i, found = 0;
0501 unsigned short addr = 0;
0502
0503 for (i = 0; !found && sch311x_ioports[i]; i++)
0504 if (sch311x_detect(sch311x_ioports[i], &addr) == 0)
0505 found++;
0506
0507 if (!found)
0508 return -ENODEV;
0509
0510 sch311x_wdt_data.runtime_reg = addr;
0511
0512 err = platform_driver_register(&sch311x_wdt_driver);
0513 if (err)
0514 return err;
0515
0516 sch311x_wdt_pdev = platform_device_register_simple(DRV_NAME, addr,
0517 NULL, 0);
0518
0519 if (IS_ERR(sch311x_wdt_pdev)) {
0520 err = PTR_ERR(sch311x_wdt_pdev);
0521 goto unreg_platform_driver;
0522 }
0523
0524 return 0;
0525
0526 unreg_platform_driver:
0527 platform_driver_unregister(&sch311x_wdt_driver);
0528 return err;
0529 }
0530
0531 static void __exit sch311x_wdt_exit(void)
0532 {
0533 platform_device_unregister(sch311x_wdt_pdev);
0534 platform_driver_unregister(&sch311x_wdt_driver);
0535 }
0536
0537 module_init(sch311x_wdt_init);
0538 module_exit(sch311x_wdt_exit);
0539
0540 MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
0541 MODULE_DESCRIPTION("SMSC SCH311x WatchDog Timer Driver");
0542 MODULE_LICENSE("GPL");