blob: 8841b52abba7bb0d1e27ee6f43160353ac898d4e [file] [log] [blame]
/* Copyright (c) 2012, The Linux Foundation. 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 version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/wcnss_wlan.h>
#include <linux/delay.h>
#include <mach/peripheral-loader.h>
#include <mach/subsystem_restart.h>
#include <mach/msm_smsm.h>
#define MODULE_NAME "wcnss_8974"
#define MAX_SSR_REASON_LEN 0x51
static int ss_restart_inprogress;
static int wcnss_crash;
static struct subsys_device *wcnss_ssr_dev;
static struct delayed_work cancel_vote_work;
#define WCNSS_APSS_WDOG_BITE_RESET_RDY_IRQ 181
static void log_wcnss_sfr(void)
{
char *smem_reset_reason;
char buffer[MAX_SSR_REASON_LEN];
unsigned smem_reset_size;
unsigned size;
smem_reset_reason = smem_get_entry(SMEM_SSR_REASON_WCNSS0,
&smem_reset_size);
if (!smem_reset_reason || !smem_reset_size) {
pr_err("%s: wcnss subsystem failure reason: %s\n",
__func__, "(unknown, smem_get_entry failed)");
} else if (!smem_reset_reason[0]) {
pr_err("%s: wcnss subsystem failure reason: %s\n",
__func__, "(unknown, init string found)");
} else {
size = smem_reset_size < MAX_SSR_REASON_LEN ? smem_reset_size :
(MAX_SSR_REASON_LEN - 1);
memcpy(buffer, smem_reset_reason, size);
buffer[size] = '\0';
pr_err("%s: wcnss subsystem failure reason: %s\n",
__func__, buffer);
memset(smem_reset_reason, 0, smem_reset_size);
wmb();
}
}
static void restart_wcnss(void)
{
log_wcnss_sfr();
subsystem_restart("wcnss");
}
static void smsm_state_cb_hdlr(void *data, uint32_t old_state,
uint32_t new_state)
{
wcnss_crash = true;
pr_err("%s: smsm state changed\n", MODULE_NAME);
if (!(new_state & SMSM_RESET))
return;
if (ss_restart_inprogress) {
pr_err("%s: Ignoring smsm reset req, restart in progress\n",
MODULE_NAME);
return;
}
ss_restart_inprogress = true;
restart_wcnss();
}
static irqreturn_t wcnss_wdog_bite_irq_hdlr(int irq, void *dev_id)
{
wcnss_crash = true;
if (ss_restart_inprogress) {
pr_err("%s: Ignoring wcnss bite irq, restart in progress\n",
MODULE_NAME);
return IRQ_HANDLED;
}
ss_restart_inprogress = true;
restart_wcnss();
return IRQ_HANDLED;
}
static void wcnss_post_bootup(struct work_struct *work)
{
struct platform_device *pdev = wcnss_get_platform_device();
struct wcnss_wlan_config *pwlanconfig = wcnss_get_wlan_config();
pr_debug(MODULE_NAME ": Cancel APPS vote for Iris & Pronto\n");
wcnss_wlan_power(&pdev->dev, pwlanconfig,
WCNSS_WLAN_SWITCH_OFF);
}
static int wcnss_shutdown(const struct subsys_desc *subsys)
{
pil_force_shutdown("wcnss");
flush_delayed_work(&cancel_vote_work);
wcnss_flush_delayed_boot_votes();
disable_irq_nosync(WCNSS_APSS_WDOG_BITE_RESET_RDY_IRQ);
return 0;
}
static int wcnss_powerup(const struct subsys_desc *subsys)
{
struct platform_device *pdev = wcnss_get_platform_device();
struct wcnss_wlan_config *pwlanconfig = wcnss_get_wlan_config();
int ret = -1;
if (pdev && pwlanconfig)
ret = wcnss_wlan_power(&pdev->dev, pwlanconfig,
WCNSS_WLAN_SWITCH_ON);
if (!ret) {
msleep(1000);
pil_force_boot("wcnss");
}
ss_restart_inprogress = false;
enable_irq(WCNSS_APSS_WDOG_BITE_RESET_RDY_IRQ);
schedule_delayed_work(&cancel_vote_work, msecs_to_jiffies(5000));
return 0;
}
/* wcnss crash handler */
static void wcnss_crash_shutdown(const struct subsys_desc *subsys)
{
pr_err("%s: crash shutdown : %d\n", MODULE_NAME, wcnss_crash);
if (wcnss_crash != true)
smsm_change_state(SMSM_APPS_STATE, SMSM_RESET, SMSM_RESET);
}
static int wcnss_ramdump(int enable,
const struct subsys_desc *crashed_subsys)
{
return 0;
}
static struct subsys_desc wcnss_ssr = {
.name = "wcnss",
.shutdown = wcnss_shutdown,
.powerup = wcnss_powerup,
.ramdump = wcnss_ramdump,
.crash_shutdown = wcnss_crash_shutdown
};
static int __init wcnss_ssr_init(void)
{
int ret;
ret = smsm_state_cb_register(SMSM_WCNSS_STATE, SMSM_RESET,
smsm_state_cb_hdlr, 0);
if (ret < 0) {
pr_err("%s: Unable to register smsm callback for wcnss Reset! %d\n",
MODULE_NAME, ret);
goto out;
}
ret = request_irq(WCNSS_APSS_WDOG_BITE_RESET_RDY_IRQ,
wcnss_wdog_bite_irq_hdlr, IRQF_TRIGGER_HIGH,
"wcnss_wdog", NULL);
if (ret < 0) {
pr_err("%s: Unable to register for wcnss bite interrupt (%d)\n",
MODULE_NAME, ret);
goto out;
}
wcnss_ssr_dev = subsys_register(&wcnss_ssr);
if (IS_ERR(wcnss_ssr_dev))
return PTR_ERR(wcnss_ssr_dev);
INIT_DELAYED_WORK(&cancel_vote_work, wcnss_post_bootup);
pr_info("%s: module initialized\n", MODULE_NAME);
out:
return ret;
}
arch_initcall(wcnss_ssr_init);