]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/watchdog/da9052_wdt.c
ENGR00162115 [WDOG]Should suspend timer in LPM
[karo-tx-linux.git] / drivers / watchdog / da9052_wdt.c
1 #include <linux/miscdevice.h>
2 #include <linux/module.h>
3 #include <linux/moduleparam.h>
4 #include <linux/fs.h>
5 #include <linux/delay.h>
6 #include <linux/timer.h>
7 #include <linux/uaccess.h>
8 #include <linux/jiffies.h>
9 #include <linux/platform_device.h>
10 #include <linux/time.h>
11 #include <linux/watchdog.h>
12 #include <linux/types.h>
13 #include <linux/kernel.h>
14
15
16 #include <linux/mfd/da9052/reg.h>
17 #include <linux/mfd/da9052/da9052.h>
18 #include <linux/mfd/da9052/wdt.h>
19
20 #define DRIVER_NAME "da9052-wdt"
21
22 #define DA9052_STROBING_FILTER_ENABLE           0x0001
23 #define DA9052_STROBING_FILTER_DISABLE          0x0002
24 #define DA9052_SET_STROBING_MODE_MANUAL         0x0004
25 #define DA9052_SET_STROBING_MODE_AUTO           0x0008
26
27 #define KERNEL_MODULE                           0
28 #define ENABLE                                  1
29 #define DISABLE                                 0
30
31 static u8 sm_strobe_filter_flag = DISABLE;
32 static u8 sm_strobe_mode_flag = DA9052_STROBE_MANUAL;
33 static u32 sm_mon_interval = DA9052_ADC_TWDMIN_TIME;
34 static u8 sm_str_req = DISABLE;
35 static u8 da9052_sm_scale = DA9052_WDT_DISABLE;
36 module_param(sm_strobe_filter_flag,  byte, 0);
37 MODULE_PARM_DESC(sm_strobe_filter_flag,
38                 "DA9052 SM driver strobe filter flag default = DISABLE");
39
40 module_param(sm_strobe_mode_flag, byte, 0);
41 MODULE_PARM_DESC(sm_strobe_mode_flag,
42                 "DA9052 SM driver watchdog strobing mode default\
43                 = DA9052_STROBE_MANUAL");
44
45 module_param(da9052_sm_scale, byte, 0);
46 MODULE_PARM_DESC(da9052_sm_scale,
47                 "DA9052 SM driver scaling value used to calculate the\
48                 time for the strobing  filter default = 0");
49
50 module_param(sm_str_req, byte, 0);
51 MODULE_PARM_DESC(sm_str_req,
52                 "DA9052 SM driver strobe request flag default = DISABLE");
53
54 static int nowayout = WATCHDOG_NOWAYOUT;
55 module_param(nowayout, int, 0);
56 MODULE_PARM_DESC(nowayout,
57                 "Watchdog cannot be stopped once started (default="
58                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
59
60 static struct timer_list *monitoring_timer;
61
62 struct da9052_wdt {
63         struct platform_device *pdev;
64         struct da9052 *da9052;
65 };
66 static struct miscdevice da9052_wdt_miscdev;
67 static unsigned long da9052_wdt_users;
68 static int da9052_wdt_expect_close;
69
70 static struct da9052_wdt *get_wdt_da9052(void)
71 {
72         /*return dev_get_drvdata(da9052_wdt_miscdev.parent);*/
73         return platform_get_drvdata
74                         (to_platform_device(da9052_wdt_miscdev.parent));
75 }
76
77 void start_strobing(struct work_struct *work)
78 {
79         struct da9052_ssc_msg msg;
80         int ret;
81         struct da9052_wdt *wdt = get_wdt_da9052();
82
83
84         if (NULL == wdt) {
85                 mod_timer(monitoring_timer, jiffies + sm_mon_interval);
86                 return;
87         }
88         msg.addr = DA9052_CONTROLD_REG;
89         msg.data = 0;
90         da9052_lock(wdt->da9052);
91         ret = wdt->da9052->read(wdt->da9052, &msg);
92         if (ret) {
93                 da9052_unlock(wdt->da9052);
94                 return;
95         }
96         da9052_unlock(wdt->da9052);
97
98         msg.data = (msg.data | DA9052_CONTROLD_WATCHDOG);
99         da9052_lock(wdt->da9052);
100         ret = wdt->da9052->write(wdt->da9052, &msg);
101         if (ret) {
102                 da9052_unlock(wdt->da9052);
103                 return;
104         }
105         da9052_unlock(wdt->da9052);
106
107         sm_str_req = DISABLE;
108
109         mod_timer(monitoring_timer, jiffies + sm_mon_interval);
110         return;
111 }
112
113
114 void timer_callback(void)
115 {
116         if (((sm_strobe_mode_flag) &&
117                 (sm_strobe_mode_flag == DA9052_STROBE_MANUAL)) ||
118                 (sm_strobe_mode_flag == DA9052_STROBE_AUTO)) {
119                 schedule_work(&strobing_action);
120         } else {
121                 if (sm_strobe_mode_flag == DA9052_STROBE_MANUAL) {
122                         mod_timer(monitoring_timer, jiffies +
123                         sm_mon_interval);
124                 }
125         }
126 }
127
128 static int da9052_sm_hw_init(struct da9052_wdt *wdt)
129 {
130         /* Create timer structure */
131         monitoring_timer = kzalloc(sizeof(struct timer_list), GFP_KERNEL);
132         if (!monitoring_timer)
133                 return -ENOMEM;
134
135         init_timer(monitoring_timer);
136         monitoring_timer->expires = jiffies + sm_mon_interval;
137         monitoring_timer->function = (void *)&timer_callback;
138
139         sm_strobe_filter_flag = DA9052_SM_STROBE_CONF;
140         sm_strobe_mode_flag = DA9052_STROBE_MANUAL;
141
142         return 0;
143 }
144
145 static int da9052_sm_hw_deinit(struct da9052_wdt *wdt)
146 {
147         struct da9052_ssc_msg msg;
148         int ret;
149
150         if (monitoring_timer != NULL)
151                 del_timer(monitoring_timer);
152         kfree(monitoring_timer);
153
154         msg.addr = DA9052_CONTROLD_REG;
155         msg.data = 0;
156
157         da9052_lock(wdt->da9052);
158         ret = wdt->da9052->read(wdt->da9052, &msg);
159         if (ret)
160                 goto ssc_err;
161         da9052_unlock(wdt->da9052);
162
163         msg.data = (msg.data & ~(DA9052_CONTROLD_TWDSCALE));
164         da9052_lock(wdt->da9052);
165         ret = wdt->da9052->write(wdt->da9052, &msg);
166         if (ret)
167                 goto ssc_err;
168         da9052_unlock(wdt->da9052);
169
170         return 0;
171 ssc_err:
172         da9052_unlock(wdt->da9052);
173         return -EIO;
174 }
175
176  s32 da9052_sm_set_strobing_filter(struct da9052_wdt *wdt,
177                                 u8 strobing_filter_state)
178  {
179         struct da9052_ssc_msg msg;
180         int ret = 0;
181
182         msg.addr = DA9052_CONTROLD_REG;
183         msg.data = 0;
184         da9052_lock(wdt->da9052);
185         ret = wdt->da9052->read(wdt->da9052, &msg);
186         if (ret)
187                 goto ssc_err;
188         da9052_unlock(wdt->da9052);
189
190         msg.data = (msg.data & DA9052_CONTROLD_TWDSCALE);
191
192         if (strobing_filter_state == ENABLE) {
193                 sm_strobe_filter_flag = ENABLE;
194                 if (DA9052_WDT_DISABLE == msg.data) {
195                         sm_str_req = DISABLE;
196                         del_timer(monitoring_timer);
197                         return 0;
198                 }
199                 if (DA9052_SCALE_64X == msg.data)
200                         sm_mon_interval = msecs_to_jiffies(DA9052_X64_WINDOW);
201                 else if (DA9052_SCALE_32X == msg.data)
202                         sm_mon_interval = msecs_to_jiffies(DA9052_X32_WINDOW);
203                 else if (DA9052_SCALE_16X == msg.data)
204                         sm_mon_interval = msecs_to_jiffies(DA9052_X16_WINDOW);
205                 else if (DA9052_SCALE_8X == msg.data)
206                         sm_mon_interval = msecs_to_jiffies(DA9052_X8_WINDOW);
207                 else if (DA9052_SCALE_4X == msg.data)
208                         sm_mon_interval = msecs_to_jiffies(DA9052_X4_WINDOW);
209                 else if (DA9052_SCALE_2X == msg.data)
210                         sm_mon_interval = msecs_to_jiffies(DA9052_X2_WINDOW);
211                 else
212                         sm_mon_interval = msecs_to_jiffies(DA9052_X1_WINDOW);
213
214         } else if (strobing_filter_state == DISABLE) {
215                 sm_strobe_filter_flag = DISABLE;
216                 sm_mon_interval = msecs_to_jiffies(DA9052_ADC_TWDMIN_TIME);
217                 if (DA9052_WDT_DISABLE == msg.data) {
218                         sm_str_req = DISABLE;
219                         del_timer(monitoring_timer);
220                         return 0;
221                 }
222         } else {
223                 return STROBING_FILTER_ERROR;
224         }
225         mod_timer(monitoring_timer, jiffies + sm_mon_interval);
226
227         return 0;
228 ssc_err:
229         da9052_unlock(wdt->da9052);
230         return -EIO;
231 }
232
233 int da9052_sm_set_strobing_mode(u8 strobing_mode_state)
234 {
235         if (strobing_mode_state == DA9052_STROBE_AUTO)
236                 sm_strobe_mode_flag = DA9052_STROBE_AUTO;
237         else if (strobing_mode_state == DA9052_STROBE_MANUAL)
238                 sm_strobe_mode_flag = DA9052_STROBE_MANUAL;
239         else
240                 return STROBING_MODE_ERROR;
241
242                 return 0;
243 }
244
245 int da9052_sm_strobe_wdt(void)
246 {
247         sm_str_req = ENABLE;
248         return 0;
249 }
250
251  s32 da9052_sm_set_wdt(struct da9052_wdt *wdt, u8 wdt_scaling)
252 {
253         struct da9052_ssc_msg msg;
254         int ret = 0;
255
256
257         if (wdt_scaling > DA9052_SCALE_64X)
258                 return INVALID_SCALING_VALUE;
259
260         msg.addr = DA9052_CONTROLD_REG;
261         msg.data = 0;
262         da9052_lock(wdt->da9052);
263         ret = wdt->da9052->read(wdt->da9052, &msg);
264         if (ret)
265                 goto ssc_err;
266         da9052_unlock(wdt->da9052);
267
268         if (!((DA9052_WDT_DISABLE == (msg.data & DA9052_CONTROLD_TWDSCALE)) &&
269             (DA9052_WDT_DISABLE == wdt_scaling))) {
270                 msg.data = (msg.data & ~(DA9052_CONTROLD_TWDSCALE));
271                 msg.addr = DA9052_CONTROLD_REG;
272
273
274                 da9052_lock(wdt->da9052);
275                 ret = wdt->da9052->write(wdt->da9052, &msg);
276                 if (ret)
277                         goto ssc_err;
278                 da9052_unlock(wdt->da9052);
279
280                 msleep(1);
281                 da9052_lock(wdt->da9052);
282                 ret = wdt->da9052->read(wdt->da9052, &msg);
283                 if (ret)
284                         goto ssc_err;
285                 da9052_unlock(wdt->da9052);
286
287
288                 msg.data |= wdt_scaling;
289
290                 da9052_lock(wdt->da9052);
291                 ret = wdt->da9052->write(wdt->da9052, &msg);
292                 if (ret)
293                         goto ssc_err;
294                 da9052_unlock(wdt->da9052);
295
296                 sm_str_req = DISABLE;
297                 if (DA9052_WDT_DISABLE == wdt_scaling) {
298                         del_timer(monitoring_timer);
299                         return 0;
300                 }
301                 if (sm_strobe_filter_flag == ENABLE) {
302                         if (DA9052_SCALE_64X == wdt_scaling) {
303                                 sm_mon_interval =
304                                         msecs_to_jiffies(DA9052_X64_WINDOW);
305                         } else if (DA9052_SCALE_32X == wdt_scaling) {
306                                 sm_mon_interval =
307                                         msecs_to_jiffies(DA9052_X32_WINDOW);
308                         } else if (DA9052_SCALE_16X == wdt_scaling) {
309                                 sm_mon_interval =
310                                         msecs_to_jiffies(DA9052_X16_WINDOW);
311                         } else if (DA9052_SCALE_8X == wdt_scaling) {
312                                 sm_mon_interval =
313                                         msecs_to_jiffies(DA9052_X8_WINDOW);
314                         } else if (DA9052_SCALE_4X == wdt_scaling) {
315                                 sm_mon_interval =
316                                         msecs_to_jiffies(DA9052_X4_WINDOW);
317                         } else if (DA9052_SCALE_2X == wdt_scaling) {
318                                 sm_mon_interval =
319                                         msecs_to_jiffies(DA9052_X2_WINDOW);
320                         } else {
321                                 sm_mon_interval =
322                                         msecs_to_jiffies(DA9052_X1_WINDOW);
323                         }
324                 } else {
325                         sm_mon_interval = msecs_to_jiffies(
326                                                 DA9052_ADC_TWDMIN_TIME);
327                 }
328                 mod_timer(monitoring_timer, jiffies + sm_mon_interval);
329         }
330
331         return 0;
332 ssc_err:
333         da9052_unlock(wdt->da9052);
334         return -EIO;
335 }
336
337 static int da9052_wdt_open(struct inode *inode, struct file *file)
338 {
339         struct da9052_wdt *wdt = get_wdt_da9052();
340         int ret;
341         printk(KERN_INFO"IN WDT OPEN \n");
342
343         if (!wdt) {
344                 printk(KERN_INFO"Returning no device\n");
345                 return -ENODEV;
346         }
347         printk(KERN_INFO"IN WDT OPEN 1\n");
348
349         if (test_and_set_bit(0, &da9052_wdt_users))
350                 return -EBUSY;
351
352         ret = da9052_sm_hw_init(wdt);
353         if (ret != 0) {
354                 printk(KERN_ERR "Watchdog hw init failed\n");
355                 return ret;
356         }
357
358         return nonseekable_open(inode, file);
359 }
360
361 static int da9052_wdt_release(struct inode *inode, struct file *file)
362 {
363         struct da9052_wdt *wdt = get_wdt_da9052();
364
365         if (da9052_wdt_expect_close == 42)
366                 da9052_sm_hw_deinit(wdt);
367         else
368                 da9052_sm_strobe_wdt();
369         da9052_wdt_expect_close = 0;
370         clear_bit(0, &da9052_wdt_users);
371         return 0;
372 }
373
374 static ssize_t da9052_wdt_write(struct file *file,
375                                 const char __user *data, size_t count,
376                                 loff_t *ppos)
377 {
378         size_t i;
379
380         if (count) {
381                 if (!nowayout) {
382                         /* In case it was set long ago */
383                         da9052_wdt_expect_close = 0;
384                         for (i = 0; i != count; i++) {
385                                 char c;
386                                 if (get_user(c, data + i))
387                                         return -EFAULT;
388                                 if (c == 'V')
389                                         da9052_wdt_expect_close = 42;
390                         }
391                 }
392                 da9052_sm_strobe_wdt();
393         }
394         return count;
395 }
396
397 static struct watchdog_info da9052_wdt_info = {
398         .options        = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
399         .identity       = "DA9052_SM Watchdog",
400 };
401
402 static long da9052_wdt_ioctl(struct file *file, unsigned int cmd,
403                                 unsigned long arg)
404 {
405         struct da9052_wdt *wdt = get_wdt_da9052();
406         void __user *argp = (void __user *)arg;
407         int __user *p = argp;
408         unsigned char new_value;
409
410         switch (cmd) {
411
412         case WDIOC_GETSUPPORT:
413                 return copy_to_user(argp, &da9052_wdt_info,
414                         sizeof(da9052_wdt_info)) ? -EFAULT : 0;
415         case WDIOC_GETSTATUS:
416         case WDIOC_GETBOOTSTATUS:
417                 return put_user(0, p);
418         case WDIOC_SETOPTIONS:
419                 if (get_user(new_value, p))
420                         return -EFAULT;
421                 if (new_value & DA9052_STROBING_FILTER_ENABLE)
422                         da9052_sm_set_strobing_filter(wdt, ENABLE);
423                 if (new_value & DA9052_STROBING_FILTER_DISABLE)
424                         da9052_sm_set_strobing_filter(wdt, DISABLE);
425                 if (new_value & DA9052_SET_STROBING_MODE_MANUAL)
426                         da9052_sm_set_strobing_mode(DA9052_STROBE_MANUAL);
427                 if (new_value & DA9052_SET_STROBING_MODE_AUTO)
428                         da9052_sm_set_strobing_mode(DA9052_STROBE_AUTO);
429                 return 0;
430         case WDIOC_KEEPALIVE:
431                 if (da9052_sm_strobe_wdt())
432                         return -EFAULT;
433                 else
434                         return 0;
435         case WDIOC_SETTIMEOUT:
436                 if (get_user(new_value, p))
437                         return -EFAULT;
438                 da9052_sm_scale = new_value;
439                 if (da9052_sm_set_wdt(wdt, da9052_sm_scale))
440                         return -EFAULT;
441         case WDIOC_GETTIMEOUT:
442                 return put_user(sm_mon_interval, p);
443         default:
444                 return -ENOTTY;
445         }
446         return 0;
447 }
448
449 static const struct file_operations da9052_wdt_fops = {
450         .owner          = THIS_MODULE,
451         .llseek         = no_llseek,
452         .unlocked_ioctl = da9052_wdt_ioctl,
453         .write          = da9052_wdt_write,
454         .open           = da9052_wdt_open,
455         .release        = da9052_wdt_release,
456 };
457
458 static struct miscdevice da9052_wdt_miscdev = {
459         .minor          = 255,
460         .name           = "da9052-wdt",
461         .fops           = &da9052_wdt_fops,
462 };
463
464 static int __devinit da9052_sm_probe(struct platform_device *pdev)
465 {
466         int ret;
467         struct da9052_wdt *wdt;
468         struct da9052_ssc_msg msg;
469
470         wdt = kzalloc(sizeof(*wdt), GFP_KERNEL);
471         if (!wdt)
472                 return -ENOMEM;
473
474         wdt->da9052 = dev_get_drvdata(pdev->dev.parent);
475         platform_set_drvdata(pdev, wdt);
476
477         msg.addr = DA9052_CONTROLD_REG;
478         msg.data = 0;
479
480         da9052_lock(wdt->da9052);
481         ret = wdt->da9052->read(wdt->da9052, &msg);
482         if (ret) {
483                 da9052_unlock(wdt->da9052);
484                 goto err_ssc_comm;
485         }
486         printk(KERN_INFO"DA9052 SM probe - 0 \n");
487
488         msg.data = (msg.data & ~(DA9052_CONTROLD_TWDSCALE));
489         ret = wdt->da9052->write(wdt->da9052, &msg);
490         if (ret) {
491                 da9052_unlock(wdt->da9052);
492                 goto err_ssc_comm;
493         }
494         da9052_unlock(wdt->da9052);
495
496         da9052_wdt_miscdev.parent = &pdev->dev;
497
498         ret = misc_register(&da9052_wdt_miscdev);
499         if (ret != 0) {
500                 platform_set_drvdata(pdev, NULL);
501                 kfree(wdt);
502                 return -EFAULT;
503         }
504         return 0;
505 err_ssc_comm:
506         platform_set_drvdata(pdev, NULL);
507         kfree(wdt);
508         return -EIO;
509 }
510
511 static int __devexit da9052_sm_remove(struct platform_device *dev)
512 {
513         misc_deregister(&da9052_wdt_miscdev);
514
515         return 0;
516 }
517
518 static struct platform_driver da9052_sm_driver = {
519         .probe          = da9052_sm_probe,
520         .remove         = __devexit_p(da9052_sm_remove),
521         .driver         = {
522                 .name   = DRIVER_NAME,
523                 .owner  = THIS_MODULE,
524         },
525 };
526
527 static int __init da9052_sm_init(void)
528 {
529         return platform_driver_register(&da9052_sm_driver);
530 }
531 module_init(da9052_sm_init);
532
533 static void __exit da9052_sm_exit(void)
534 {
535         platform_driver_unregister(&da9052_sm_driver);
536 }
537 module_exit(da9052_sm_exit);
538
539 MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>")
540 MODULE_DESCRIPTION("DA9052 SM Device Driver");
541 MODULE_LICENSE("GPL");
542 MODULE_ALIAS("platform:" DRIVER_NAME);