blob: d464b5a513c41ecd4b290a394f9714cfc86b5d00 [file] [log] [blame]
/*******************************************************************************
* Copyright 2010 Broadcom Corporation. All rights reserved.
*
* @file drivers/watchdog/kona-sec-wd.c
*
* Unless you and Broadcom execute a separate written software license agreement
* governing use of this software, this software is licensed to you under the
* terms of the GNU General Public License version 2, available at
* http://www.gnu.org/copyleft/gpl.html (the "GPL").
*
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a license
* other than the GPL, without Broadcom's express prior written consent.
*******************************************************************************/
/**
* @file
* driver/watchdog/kona-sec-wd.c
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <mach/sec_api.h>
#include <linux/platform_device.h>
#include <mach/io_map.h>
#include <plat/kona_reset_reason.h>
#include <mach/rdb_A0/brcm_rdb_root_rst_mgr_reg.h>
#include <linux/types.h>
#include <linux/io.h>
#include <mach/sram_config.h>
#include <plat/cdc.h>
#define SEC_EXIT_NORMAL 1
#define SEC_WD_TAP_DELAY 12000
#define SSAPI_ACTIVATE_SEC_WATCHDOG 0x01000006
#define SSAPI_ENABLE_SEC_WATCHDOG 0x01000007
#define SSAPI_DISABLE_SEC_WATCHDOG 0x01000008
#define SSAPI_PAT_SEC_WATCHDOG 0x01000009
enum sec_wd_state_e {
SEC_WD_DISABLE = 0,
SEC_WD_ENABLE = 1,
WD_STATES,
SEC_WD_PAT_BEGIN = 0,
SEC_WD_PAT_DONE,
SEC_WD_SET_NEXT_EVENT,
SEC_WD_SET_RETY,
PAT_STATES,
};
struct track_sec_wd {
struct timeval trk_sec_wd_pat[PAT_STATES];
struct timeval trk_sec_wd_on[WD_STATES];
};
struct sec_wd_core_st {
struct workqueue_struct *sec_wd_wq;
struct delayed_work sec_wd_work;
struct notifier_block sec_wd_panic_block;
struct notifier_block sec_wd_reboot_block;
struct track_sec_wd trk_sec_wd;
unsigned int sec_wd_enabled_mode;
unsigned int is_sec_wd_on;
unsigned int is_sec_pat_done;
unsigned int cdc_error_count;
unsigned int sec_pat_retry;
};
static struct sec_wd_core_st sec_wd_core;
static void sec_wd_pat(struct work_struct *work)
{
struct sec_wd_core_st *swdc = &sec_wd_core;
if (swdc->is_sec_wd_on == SEC_WD_ENABLE) {
swdc->sec_pat_retry = 0;
swdc->is_sec_pat_done = SEC_WD_PAT_BEGIN;
do_gettimeofday(&swdc->
trk_sec_wd.trk_sec_wd_pat[SEC_WD_PAT_BEGIN]);
printk(KERN_ALERT "patting watchdog\n");
secure_api_call_local(SSAPI_PAT_SEC_WATCHDOG);
swdc->is_sec_pat_done = SEC_WD_PAT_DONE;
do_gettimeofday(&swdc->
trk_sec_wd.trk_sec_wd_pat[SEC_WD_PAT_DONE]);
queue_delayed_work_on(0, swdc->sec_wd_wq, &swdc->sec_wd_work,
msecs_to_jiffies(SEC_WD_TAP_DELAY));
swdc->is_sec_pat_done = SEC_WD_SET_NEXT_EVENT;
do_gettimeofday(&swdc->
trk_sec_wd.trk_sec_wd_pat[SEC_WD_SET_NEXT_EVENT]);
} else {
printk(KERN_ALERT "patting watchdog disable\n");
}
}
static void init_patter(void)
{
struct sec_wd_core_st *swdc = &sec_wd_core;
INIT_DELAYED_WORK(&swdc->sec_wd_work, sec_wd_pat);
queue_delayed_work_on(0, swdc->sec_wd_wq, &swdc->sec_wd_work,
msecs_to_jiffies(SEC_WD_TAP_DELAY));
}
void sec_wd_suspend(void)
{
int ret;
struct sec_wd_core_st *swdc = &sec_wd_core;
if (swdc->is_sec_wd_on == SEC_WD_DISABLE)
return;
ret = secure_api_call_local(SSAPI_DISABLE_SEC_WATCHDOG);
if (ret == 1) {
swdc->is_sec_wd_on = SEC_WD_DISABLE;
do_gettimeofday(&swdc->
trk_sec_wd.trk_sec_wd_on[SEC_WD_DISABLE]);
printk(KERN_ALERT "___suspend:disabling secure watchdog...done\n");
} else {
printk(KERN_ALERT "___suspend:disabling secure watchdog...fail\n");
}
}
EXPORT_SYMBOL(sec_wd_suspend);
void sec_wd_resume(void)
{
int ret;
struct sec_wd_core_st *swdc = &sec_wd_core;
if (swdc->is_sec_wd_on == SEC_WD_ENABLE)
return;
ret = secure_api_call_local(SSAPI_ENABLE_SEC_WATCHDOG);
if (ret == 1) {
swdc->is_sec_wd_on = SEC_WD_ENABLE;
do_gettimeofday(&swdc->trk_sec_wd.trk_sec_wd_on[SEC_WD_ENABLE]);
printk(KERN_ALERT "___resume:enabling secure watchdog...done\n");
} else {
printk(KERN_ERR "___resume:enabling secure watchdog...fail\n");
}
}
EXPORT_SYMBOL(sec_wd_resume);
void sec_wd_enable(void)
{
int ret;
struct sec_wd_core_st *swdc = &sec_wd_core;
if (swdc->is_sec_wd_on == SEC_WD_ENABLE)
return;
ret = secure_api_call(SSAPI_ENABLE_SEC_WATCHDOG, 0, 0, 0, 0);
if (ret == 1) {
swdc->is_sec_wd_on = SEC_WD_ENABLE;
do_gettimeofday(&swdc->trk_sec_wd.trk_sec_wd_on[SEC_WD_ENABLE]);
printk(KERN_ALERT "___resume:enabling secure watchdog...done\n");
queue_delayed_work_on(0, swdc->sec_wd_wq, &swdc->sec_wd_work,
msecs_to_jiffies(SEC_WD_TAP_DELAY));
} else {
printk(KERN_ERR "___resume:enabling secure watchdog...fail\n");
}
}
EXPORT_SYMBOL(sec_wd_enable);
void sec_wd_disable(void)
{
int ret;
struct sec_wd_core_st *swdc = &sec_wd_core;
if (swdc->is_sec_wd_on == SEC_WD_DISABLE)
return;
/* we do not need following piece of code
* because disable watchdog is happening in
* kona_mach_pm_enter.
* let us remove it after extensive testing. */
#if 0
ret = cancel_delayed_work(&swdc->sec_wd_work);
if (ret == 0) {
printk(KERN_ALERT "secure watchdog wq could not be cancelled\n");
/* workq instance might be running, wait for it */
flush_workqueue(swdc->sec_wd_wq);
}
#endif
/* at this point we are absolutely sure that,
no work function is running anywhere in the system.
this is the safest place to disabled watchdog. */
ret = secure_api_call(SSAPI_DISABLE_SEC_WATCHDOG,
0, 0, 0, 0);
if (ret == 1) {
do_gettimeofday(&swdc->
trk_sec_wd.trk_sec_wd_on[SEC_WD_DISABLE]);
printk(KERN_ALERT "___suspend:disabling secure watchdog...done\n");
swdc->is_sec_wd_on = SEC_WD_DISABLE;
} else {
printk(KERN_ALERT "___suspend:disabling secure watchdog...fail\n");
}
}
EXPORT_SYMBOL(sec_wd_disable);
static int sec_wd_panic_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
secure_api_call(SSAPI_DISABLE_SEC_WATCHDOG, 0, 0, 0, 0);
return NOTIFY_DONE;
}
static int sec_wd_reboot_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
secure_api_call(SSAPI_DISABLE_SEC_WATCHDOG, 0, 0, 0, 0);
return NOTIFY_DONE;
}
int is_soft_reset_boot(void)
{
u32 reg;
int soft_rst;
reg = readl(KONA_ROOT_RST_VA + ROOT_RST_MGR_REG_RSTSTS_OFFSET);
soft_rst = (reg & ROOT_RST_MGR_REG_RSTSTS_CHIPSFTRST_DET_MASK)
>> ROOT_RST_MGR_REG_RSTSTS_CHIPSFTRST_DET_SHIFT;
return soft_rst;
}
int is_power_on_reset(void)
{
unsigned int *reset_reason = (unsigned int *)
ioremap(SRAM_RST_REASON_BASE, 0x4);
unsigned int rst;
pr_debug("%s: reset_reason 0x%x\n", __func__, *reset_reason);
rst = (*reset_reason) & RST_REASON_MASK;
iounmap(reset_reason);
if (rst == POWERON_RESET)
return 1;
else
return 0;
}
static int __devinit sec_wd_probe(struct platform_device *pdev)
{
struct sec_wd_core_st *swdc = &sec_wd_core;
swdc->sec_wd_enabled_mode = 0;
if (is_soft_reset_boot() || is_power_on_reset()) {
swdc->sec_wd_enabled_mode = 1;
swdc->sec_wd_panic_block.notifier_call = sec_wd_panic_event;
swdc->sec_wd_panic_block.priority = INT_MAX;
swdc->sec_wd_reboot_block.notifier_call = sec_wd_reboot_event;
swdc->sec_wd_reboot_block.priority = INT_MAX;
swdc->is_sec_wd_on = SEC_WD_DISABLE;
swdc->cdc_error_count = 0;
swdc->sec_pat_retry = 0;
atomic_notifier_chain_register(&panic_notifier_list,
&swdc->sec_wd_panic_block);
blocking_notifier_chain_register(&reboot_notifier_list,
&swdc->sec_wd_reboot_block);
swdc->sec_wd_wq = create_singlethread_workqueue("sec_wd_wq");
if (!(swdc->sec_wd_wq)) {
printk(KERN_ALERT "___sec_wd_probe:alloc workqueue failed\n");
return -ENOMEM;
}
} else
printk(KERN_ALERT "_______disabling Sec watchdog_______\n");
printk(KERN_ALERT "_______sec_wd_probe succesfull_______\n");
return 0;
}
unsigned int sec_wd_activate(void)
{
struct sec_wd_core_st *swdc = &sec_wd_core;
printk(KERN_ALERT "initializing secure watchdog....");
swdc->is_sec_wd_on = SEC_WD_ENABLE;
printk(KERN_ALERT "Active\n");
secure_api_call(SSAPI_ACTIVATE_SEC_WATCHDOG, 0, 0, 0, 0);
init_patter();
return 0;
}
EXPORT_SYMBOL(sec_wd_activate);
unsigned int is_sec_wd_enabled(void)
{
struct sec_wd_core_st *swdc = &sec_wd_core;
return swdc->sec_wd_enabled_mode;
}
EXPORT_SYMBOL(is_sec_wd_enabled);
static int __devexit sec_wd_remove(struct platform_device *pdev);
static const struct of_device_id sec_wd_match[] = {
{ .compatible = "bcm,secure_watchdog" },
{ /* Sentinel */ }
};
static struct platform_driver sec_wd_pltfm_driver = {
.driver = {
.name = "secure_watchdog",
.owner = THIS_MODULE,
.of_match_table = sec_wd_match,
},
.probe = sec_wd_probe,
.remove = __devexit_p(sec_wd_remove),
};
static int __devexit sec_wd_remove(struct platform_device *pdev)
{
if (is_sec_wd_enabled())
sec_wd_disable();
return 0;
}
static int __init sec_wd_init(void)
{
return platform_driver_register(&sec_wd_pltfm_driver);
}
static void __exit sec_wd_exit(void)
{
platform_driver_unregister(&sec_wd_pltfm_driver);
}
module_init(sec_wd_init);
module_exit(sec_wd_exit);
MODULE_AUTHOR("Oza");
MODULE_DESCRIPTION("Secure Watchdog");
MODULE_LICENSE("GPL");