blob: e528650ac8c2137238aa80cece0bb8edc95c8e52 [file] [log] [blame]
/* Copyright (c) 2012, Code Aurora Forum. 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/reboot.h>
#include <linux/workqueue.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/stringify.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <mach/irqs.h>
#include <mach/msm_smsm.h>
#include <mach/scm.h>
#include <mach/peripheral-loader.h>
#include <mach/subsystem_restart.h>
#include <mach/subsystem_notif.h>
#include <mach/socinfo.h>
#include "smd_private.h"
#include "modem_notifier.h"
#include "ramdump.h"
static struct gss_8064_data {
struct miscdevice gss_dev;
void *pil_handle;
void *gss_ramdump_dev;
void *smem_ramdump_dev;
} gss_data;
static int crash_shutdown;
static struct subsys_device *gss_8064_dev;
#define MAX_SSR_REASON_LEN 81U
static void log_gss_sfr(void)
{
u32 size;
char *smem_reason, reason[MAX_SSR_REASON_LEN];
smem_reason = smem_get_entry(SMEM_SSR_REASON_MSS0, &size);
if (!smem_reason || !size) {
pr_err("GSS subsystem failure reason: (unknown, smem_get_entry failed).\n");
return;
}
if (!smem_reason[0]) {
pr_err("GSS subsystem failure reason: (unknown, init string found).\n");
return;
}
size = min(size, MAX_SSR_REASON_LEN-1);
memcpy(reason, smem_reason, size);
reason[size] = '\0';
pr_err("GSS subsystem failure reason: %s.\n", reason);
smem_reason[0] = '\0';
wmb();
}
static void restart_gss(void)
{
log_gss_sfr();
subsystem_restart_dev(gss_8064_dev);
}
static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state)
{
/* Ignore if we're the one that set SMSM_RESET */
if (crash_shutdown)
return;
if (new_state & SMSM_RESET) {
pr_err("GSS SMSM state changed to SMSM_RESET.\n"
"Probable err_fatal on the GSS. "
"Calling subsystem restart...\n");
restart_gss();
}
}
#define Q6_FW_WDOG_ENABLE 0x08882024
#define Q6_SW_WDOG_ENABLE 0x08982024
static int gss_shutdown(const struct subsys_desc *desc)
{
pil_force_shutdown("gss");
disable_irq_nosync(GSS_A5_WDOG_EXPIRED);
return 0;
}
static int gss_powerup(const struct subsys_desc *desc)
{
pil_force_boot("gss");
enable_irq(GSS_A5_WDOG_EXPIRED);
return 0;
}
void gss_crash_shutdown(const struct subsys_desc *desc)
{
crash_shutdown = 1;
smsm_reset_modem(SMSM_RESET);
}
/* FIXME: Get address, size from PIL */
static struct ramdump_segment gss_segments[] = {
{0x89000000, 0x00D00000}
};
static struct ramdump_segment smem_segments[] = {
{0x80000000, 0x00200000},
};
static int gss_ramdump(int enable,
const struct subsys_desc *crashed_subsys)
{
int ret = 0;
if (enable) {
ret = do_ramdump(gss_data.gss_ramdump_dev, gss_segments,
ARRAY_SIZE(gss_segments));
if (ret < 0) {
pr_err("Unable to dump gss memory (rc = %d).\n",
ret);
goto out;
}
ret = do_ramdump(gss_data.smem_ramdump_dev, smem_segments,
ARRAY_SIZE(smem_segments));
if (ret < 0) {
pr_err("Unable to dump smem memory (rc = %d).\n", ret);
goto out;
}
}
out:
return ret;
}
static irqreturn_t gss_wdog_bite_irq(int irq, void *dev_id)
{
pr_err("Watchdog bite received from GSS!\n");
restart_gss();
return IRQ_HANDLED;
}
static struct subsys_desc gss_8064 = {
.name = "gss",
.shutdown = gss_shutdown,
.powerup = gss_powerup,
.ramdump = gss_ramdump,
.crash_shutdown = gss_crash_shutdown
};
static int gss_subsystem_restart_init(void)
{
gss_8064_dev = subsys_register(&gss_8064);
if (IS_ERR(gss_8064_dev))
return PTR_ERR(gss_8064_dev);
return 0;
}
static int gss_open(struct inode *inode, struct file *filep)
{
void *ret;
gss_data.pil_handle = ret = pil_get("gss");
if (!ret)
pr_debug("%s - pil_get returned NULL\n", __func__);
return 0;
}
static int gss_release(struct inode *inode, struct file *filep)
{
pil_put(gss_data.pil_handle);
pr_debug("%s pil_put called on GSS\n", __func__);
return 0;
}
const struct file_operations gss_file_ops = {
.open = gss_open,
.release = gss_release,
};
static int __init gss_8064_init(void)
{
int ret;
if (!cpu_is_apq8064())
return -ENODEV;
ret = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_RESET,
smsm_state_cb, 0);
if (ret < 0)
pr_err("%s: Unable to register SMSM callback! (%d)\n",
__func__, ret);
ret = request_irq(GSS_A5_WDOG_EXPIRED, gss_wdog_bite_irq,
IRQF_TRIGGER_RISING, "gss_a5_wdog", NULL);
if (ret < 0) {
pr_err("%s: Unable to request gss watchdog IRQ. (%d)\n",
__func__, ret);
disable_irq_nosync(GSS_A5_WDOG_EXPIRED);
goto out;
}
ret = gss_subsystem_restart_init();
if (ret < 0) {
pr_err("%s: Unable to reg with subsystem restart. (%d)\n",
__func__, ret);
goto out;
}
gss_data.gss_dev.minor = MISC_DYNAMIC_MINOR;
gss_data.gss_dev.name = "gss";
gss_data.gss_dev.fops = &gss_file_ops;
ret = misc_register(&gss_data.gss_dev);
if (ret) {
pr_err("%s: misc_registers failed for %s (%d)", __func__,
gss_data.gss_dev.name, ret);
goto out;
}
gss_data.gss_ramdump_dev = create_ramdump_device("gss");
if (!gss_data.gss_ramdump_dev) {
pr_err("%s: Unable to create gss ramdump device. (%d)\n",
__func__, -ENOMEM);
ret = -ENOMEM;
goto out;
}
gss_data.smem_ramdump_dev = create_ramdump_device("smem-gss");
if (!gss_data.smem_ramdump_dev) {
pr_err("%s: Unable to create smem ramdump device. (%d)\n",
__func__, -ENOMEM);
ret = -ENOMEM;
goto out;
}
pr_info("%s: gss fatal driver init'ed.\n", __func__);
out:
return ret;
}
module_init(gss_8064_init);