blob: 5bdc03e14972cf92d69212ed39dd62d38e15dd72 [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 <asm/uaccess.h>
#include <asm/io.h>
#include <mach/init-dropcam.h>
#define DC_WDT_ENABLE_GPIO 12
#define DC_WDT_RESET_GPIO 54
#define DC_WDT_HALF_CYCLE_TIME ((HZ * 550)/1000)
#define KERNEL_HEARTBEAT_EXPIRE_TIMEOUT (HZ * 10) /* 10 seconds */
static struct workqueue_struct *tps3813k33_kernel_heartbeat;
struct dropcam_tps3813k33_info {
int last_toggle_value;
struct timer_list * ptimer;
unsigned long last_kernel_heartbeat;
struct work_struct tps3813k33_work;
};
static struct timer_list dropcam_tps3813k33_timer;
static void dropcam_tps3813k33_heartbeat_worker(struct work_struct *work) {
struct dropcam_tps3813k33_info *wdt_info = container_of(work, struct dropcam_tps3813k33_info, tps3813k33_work);
wdt_info->last_kernel_heartbeat = jiffies;
}
static void dropcam_tps3813k33_toggle_timer_handler(unsigned long data)
{
struct dropcam_tps3813k33_info *wdt_info =
(struct dropcam_tps3813k33_info *)data;
queue_work(tps3813k33_kernel_heartbeat, &wdt_info->tps3813k33_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;
ambarella_gpio_set(DC_WDT_RESET_GPIO, wdt_info->last_toggle_value);
ambarella_gpio_set(DC_WDT_ENABLE_GPIO, 1);
mod_timer(wdt_info->ptimer, jiffies + DC_WDT_HALF_CYCLE_TIME);
}
}
static const struct file_operations dropcam_tps3813k33_fops = {
.owner = THIS_MODULE,
};
static int __devinit dropcam_tps3813k33_probe(struct platform_device *pdev)
{
int errorCode = 0;
struct dropcam_tps3813k33_info *pinfo;
if (!((AMBARELLA_BOARD_TYPE(system_rev) == AMBARELLA_BOARD_TYPE_VENDOR) &&
((AMBARELLA_BOARD_REV(system_rev) == DROPCAM_BOARD_REV_CROWN_ROYAL) ||
(AMBARELLA_BOARD_REV(system_rev) == DROPCAM_BOARD_REV_QUARTZ)))) {
printk(KERN_WARNING "dropcam_tps3813k33_probe: Wrong system type, not loading\n");
errorCode = -ENXIO;
goto dropcam_tps3813k33_no_dev;
}
pinfo = kzalloc(sizeof(struct dropcam_tps3813k33_info), GFP_KERNEL);
if (pinfo == NULL) {
printk(KERN_ERR "dropcam_tps3813k33_probe: out of memory\n");
errorCode = -ENOMEM;
goto dropcam_tps3813k33_no_pinfo;
}
pinfo->last_toggle_value = 0;
pinfo->ptimer = &dropcam_tps3813k33_timer;
init_timer(pinfo->ptimer);
pinfo->ptimer->function = dropcam_tps3813k33_toggle_timer_handler;
pinfo->ptimer->data = (unsigned long)pinfo;
pinfo->ptimer->expires = jiffies + DC_WDT_HALF_CYCLE_TIME;
tps3813k33_kernel_heartbeat = create_workqueue("tps3813k33_kernel_heartbeat");
INIT_WORK(&pinfo->tps3813k33_work, dropcam_tps3813k33_heartbeat_worker);
printk(KERN_INFO "Enabling TPS3813 HW WDT\n");
ambarella_gpio_config(DC_WDT_ENABLE_GPIO, GPIO_FUNC_SW_OUTPUT);
ambarella_gpio_config(DC_WDT_RESET_GPIO, GPIO_FUNC_SW_OUTPUT);
ambarella_gpio_set(DC_WDT_RESET_GPIO, pinfo->last_toggle_value);
add_timer(pinfo->ptimer);
pinfo->last_kernel_heartbeat = jiffies;
goto dropcam_tps3813k33_ok;
dropcam_tps3813k33_no_pinfo:
dropcam_tps3813k33_no_dev:
dropcam_tps3813k33_ok:
return errorCode;
}
static int __devexit dropcam_tps3813k33_remove(struct platform_device *pdev)
{
struct dropcam_tps3813k33_info *pinfo;
int errorCode = 0;
pinfo = platform_get_drvdata(pdev);
printk(KERN_INFO "Disabling TPS3813 HW WDT\n");
ambarella_gpio_set(DC_WDT_ENABLE_GPIO, 0);
del_timer(&dropcam_tps3813k33_timer);
if (pinfo)
{
kfree(pinfo);
}
return errorCode;
}
static int __devexit dropcam_tps3813k33_shutdown(struct platform_device *pdev)
{
struct dropcam_tps3813k33_info *pinfo;
pinfo = platform_get_drvdata(pdev);
printk(KERN_INFO "Kicking TPS3813 HW WDT before reboot\n");
ambarella_gpio_set(DC_WDT_RESET_GPIO, 1);
ambarella_gpio_set(DC_WDT_RESET_GPIO, 0);
del_timer(&dropcam_tps3813k33_timer);
if (pinfo)
{
kfree(pinfo);
}
return 0;
}
static int dropcam_tps3813k33_release(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver dropcam_tps3813k33_driver = {
.probe = dropcam_tps3813k33_probe,
.remove = __devexit_p(dropcam_tps3813k33_remove),
.shutdown = __devexit_p(dropcam_tps3813k33_shutdown),
.driver = {
.owner = THIS_MODULE,
.name = "dropcam-tps3813k33",
},
};
static struct platform_device dropcam_tps3813k33_device = {
.name = "dropcam-tps3813k33",
.id = 0,
.dev = {
.release = dropcam_tps3813k33_release,
},
};
static int __init dropcam_tps3813k33_init(void)
{
platform_device_register(&dropcam_tps3813k33_device);
return platform_driver_register(&dropcam_tps3813k33_driver);
}
static void __exit dropcam_tps3813k33_exit(void)
{
platform_device_unregister(&dropcam_tps3813k33_device);
platform_driver_unregister(&dropcam_tps3813k33_driver);
}
core_initcall(dropcam_tps3813k33_init);
module_exit(dropcam_tps3813k33_exit);
MODULE_DESCRIPTION("Dropcam TPS3813K33 Watch Dog Timer");
MODULE_AUTHOR("Dan Girellini, <dan@dropcam.com>");
MODULE_LICENSE("GPL");