| /* |
| * Copyright (C) 2013-2014 Dropcam |
| * All rights reserved. |
| * |
| * 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; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/types.h> |
| #include <linux/timer.h> |
| #include <linux/miscdevice.h> |
| #include <linux/watchdog.h> |
| #include <linux/fs.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <linux/interrupt.h> |
| #include <linux/clk.h> |
| #include <linux/slab.h> |
| #include <linux/workqueue.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| |
| #include <asm/uaccess.h> |
| #include <asm/io.h> |
| |
| #define TPS3813_WDT_HALF_CYCLE_TIME ((HZ * 550)/1000) |
| #define KERNEL_HEARTBEAT_EXPIRE_TIMEOUT (HZ * 10) /* 10 seconds */ |
| |
| static struct workqueue_struct *tps3813_kernel_heartbeat; |
| |
| struct tps3813_info { |
| int last_toggle_value; |
| struct timer_list * ptimer; |
| unsigned long last_kernel_heartbeat; |
| struct work_struct tps3813_work; |
| |
| int wdi_gpio; |
| int enable_gpio; |
| }; |
| |
| static struct timer_list tps3813_timer; |
| |
| static void tps3813_heartbeat_worker(struct work_struct *work) { |
| struct tps3813_info *wdt_info = container_of(work, struct tps3813_info, tps3813_work); |
| wdt_info->last_kernel_heartbeat = jiffies; |
| } |
| |
| static void tps3813_toggle_timer_handler(unsigned long data) |
| { |
| struct tps3813_info *wdt_info = |
| (struct tps3813_info *)data; |
| |
| queue_work(tps3813_kernel_heartbeat, &wdt_info->tps3813_work); |
| |
| /* this timer interrupt handler might still fire after kernel panic |
| * kick the watchdog only if the kernel is alive */ |
| if (jiffies - wdt_info->last_kernel_heartbeat < KERNEL_HEARTBEAT_EXPIRE_TIMEOUT) { |
| wdt_info->last_toggle_value = !wdt_info->last_toggle_value; |
| |
| gpio_set_value(wdt_info->wdi_gpio, wdt_info->last_toggle_value); |
| |
| mod_timer(wdt_info->ptimer, jiffies + TPS3813_WDT_HALF_CYCLE_TIME); |
| } |
| } |
| |
| struct tps3813_info *tps3813_of_populate_pdata(struct device *dev, |
| struct device_node *np) |
| { |
| struct tps3813_info *pdata; |
| pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) |
| return ERR_PTR(-ENOMEM); |
| |
| pdata->wdi_gpio = of_get_named_gpio(np, "wdi-gpio", 0); |
| pdata->enable_gpio = of_get_named_gpio(np, "enable-gpio", 0); |
| |
| return pdata; |
| } |
| EXPORT_SYMBOL_GPL(tps3813_of_populate_pdata); |
| |
| static int tps3813_probe(struct platform_device *pdev) |
| { |
| int errorCode = 0; |
| struct tps3813_info *pdata; |
| struct device_node *np = pdev->dev.of_node; |
| |
| if (np) { |
| pdata = tps3813_of_populate_pdata(&pdev->dev, np); |
| if (IS_ERR(pdata)) |
| return PTR_ERR(pdata); |
| } else { |
| dev_err(&pdev->dev, "no platform data\n"); |
| return -EINVAL; |
| } |
| |
| dev_info(&pdev->dev, "Enabling WDT"); |
| errorCode = gpio_request(pdata->enable_gpio, dev_name(&pdev->dev)); |
| |
| if (errorCode == -EPROBE_DEFER) |
| return errorCode; |
| |
| if (errorCode) { |
| dev_err(&pdev->dev, "Failed to request enable gpio: %d\n", errorCode); |
| goto err_gpio_free; |
| } |
| errorCode = gpio_direction_output(pdata->enable_gpio, 1); |
| if (errorCode) { |
| dev_err(&pdev->dev, "Failed to set enable gpio to output: %d\n", errorCode); |
| goto err_gpio_free; |
| } |
| |
| errorCode = gpio_request(pdata->wdi_gpio, dev_name(&pdev->dev)); |
| if (errorCode) { |
| dev_err(&pdev->dev, "Failed to request enable gpio: %d\n", errorCode); |
| goto err_gpio_free; |
| } |
| errorCode = gpio_direction_output(pdata->wdi_gpio, pdata->last_toggle_value); |
| if (errorCode) { |
| dev_err(&pdev->dev, "Failed to set enable gpio to output: %d\n", errorCode); |
| goto err_gpio_free; |
| } |
| |
| pdata->last_toggle_value = 0; |
| |
| pdata->ptimer = &tps3813_timer; |
| init_timer(pdata->ptimer); |
| |
| pdata->ptimer->function = tps3813_toggle_timer_handler; |
| pdata->ptimer->data = (unsigned long)pdata; |
| |
| pdata->ptimer->expires = jiffies + TPS3813_WDT_HALF_CYCLE_TIME; |
| |
| tps3813_kernel_heartbeat = create_workqueue("tps3813_kernel_heartbeat"); |
| INIT_WORK(&pdata->tps3813_work, tps3813_heartbeat_worker); |
| |
| add_timer(pdata->ptimer); |
| pdata->last_kernel_heartbeat = jiffies; |
| |
| // do first kick |
| tps3813_toggle_timer_handler((unsigned long)pdata); |
| |
| platform_set_drvdata(pdev, pdata); |
| |
| goto tps3813_ok; |
| |
| err_gpio_free: |
| if (gpio_is_valid(pdata->enable_gpio)) |
| gpio_free(pdata->enable_gpio); |
| |
| if (gpio_is_valid(pdata->wdi_gpio)) |
| gpio_free(pdata->wdi_gpio); |
| |
| tps3813_ok: |
| return errorCode; |
| } |
| |
| |
| static int tps3813_remove(struct platform_device *pdev) |
| { |
| struct tps3813_info *pinfo; |
| int errorCode = 0; |
| |
| pinfo = platform_get_drvdata(pdev); |
| |
| dev_info(&pdev->dev, "Disabling WDT"); |
| |
| gpio_set_value(pinfo->enable_gpio, 0); |
| |
| destroy_workqueue(tps3813_kernel_heartbeat); |
| del_timer(&tps3813_timer); |
| |
| gpio_free(pinfo->enable_gpio); |
| gpio_free(pinfo->wdi_gpio); |
| |
| kfree(pinfo); |
| return errorCode; |
| } |
| |
| static void tps3813_shutdown(struct platform_device *pdev) |
| { |
| dev_info(&pdev->dev, "Disabling WDT"); |
| del_timer(&tps3813_timer); |
| } |
| |
| static const struct of_device_id tps3813_match[] = { |
| { .compatible = "tps3813" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, tps3813_match); |
| |
| static struct platform_driver tps3813_driver = { |
| .probe = tps3813_probe, |
| .remove = tps3813_remove, |
| .shutdown = tps3813_shutdown, |
| .driver = { |
| .name = "tps3813", |
| .owner = THIS_MODULE, |
| .of_match_table = tps3813_match, |
| }, |
| }; |
| |
| static int __init tps3813_init(void) |
| { |
| return platform_driver_register(&tps3813_driver); |
| } |
| |
| static void __exit tps3813_exit(void) |
| { |
| platform_driver_unregister(&tps3813_driver); |
| } |
| |
| module_init(tps3813_init); |
| module_exit(tps3813_exit); |
| |
| MODULE_DESCRIPTION("TPS3813 Watch Dog Timer"); |
| MODULE_AUTHOR("Jonathan Miles, <jonathan.miles@teknique.com>"); |
| MODULE_LICENSE("GPL"); |