blob: 369c49000c627b5eaafff8a286dfcc9502964065 [file] [log] [blame]
/*
* 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");