489 lines
12 KiB
C
489 lines
12 KiB
C
/* Copyright (c) 2010-2013, 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/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/workqueue.h>
|
|
#include <asm/mach-types.h>
|
|
#include <asm/uaccess.h>
|
|
#include <linux/mfd/pm8xxx/misc.h>
|
|
#include <mach/mdm.h>
|
|
#include <mach/restart.h>
|
|
#include <mach/subsystem_notif.h>
|
|
#include <mach/subsystem_restart.h>
|
|
#include <linux/msm_charm.h>
|
|
#include "msm_watchdog.h"
|
|
#include "devices.h"
|
|
|
|
#define CHARM_MODEM_TIMEOUT 6000
|
|
#define CHARM_HOLD_TIME 4000
|
|
#define CHARM_MODEM_DELTA 100
|
|
|
|
static void (*power_on_charm)(void);
|
|
static void (*power_down_charm)(void);
|
|
|
|
static int charm_debug_on;
|
|
static int charm_status_irq;
|
|
static int charm_errfatal_irq;
|
|
static int charm_ready;
|
|
static enum charm_boot_type boot_type = CHARM_NORMAL_BOOT;
|
|
static int charm_boot_status;
|
|
static int charm_ram_dump_status;
|
|
static struct workqueue_struct *charm_queue;
|
|
|
|
#define CHARM_DBG(...) do { if (charm_debug_on) \
|
|
pr_info(__VA_ARGS__); \
|
|
} while (0);
|
|
|
|
|
|
DECLARE_COMPLETION(charm_needs_reload);
|
|
DECLARE_COMPLETION(charm_boot);
|
|
DECLARE_COMPLETION(charm_ram_dumps);
|
|
|
|
static void charm_disable_irqs(void)
|
|
{
|
|
disable_irq_nosync(charm_errfatal_irq);
|
|
disable_irq_nosync(charm_status_irq);
|
|
|
|
}
|
|
|
|
static int charm_subsys_shutdown(const struct subsys_desc *crashed_subsys)
|
|
{
|
|
charm_ready = 0;
|
|
power_down_charm();
|
|
return 0;
|
|
}
|
|
|
|
static int charm_subsys_powerup(const struct subsys_desc *crashed_subsys)
|
|
{
|
|
power_on_charm();
|
|
boot_type = CHARM_NORMAL_BOOT;
|
|
complete(&charm_needs_reload);
|
|
wait_for_completion(&charm_boot);
|
|
pr_info("%s: charm modem has been restarted\n", __func__);
|
|
INIT_COMPLETION(charm_boot);
|
|
return charm_boot_status;
|
|
}
|
|
|
|
static int charm_subsys_ramdumps(int want_dumps,
|
|
const struct subsys_desc *crashed_subsys)
|
|
{
|
|
charm_ram_dump_status = 0;
|
|
if (want_dumps) {
|
|
boot_type = CHARM_RAM_DUMPS;
|
|
complete(&charm_needs_reload);
|
|
wait_for_completion(&charm_ram_dumps);
|
|
INIT_COMPLETION(charm_ram_dumps);
|
|
power_down_charm();
|
|
}
|
|
return charm_ram_dump_status;
|
|
}
|
|
|
|
static struct subsys_device *charm_subsys;
|
|
|
|
static struct subsys_desc charm_subsystem = {
|
|
.shutdown = charm_subsys_shutdown,
|
|
.ramdump = charm_subsys_ramdumps,
|
|
.powerup = charm_subsys_powerup,
|
|
.name = "external_modem",
|
|
};
|
|
|
|
static int charm_panic_prep(struct notifier_block *this,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
int i;
|
|
|
|
CHARM_DBG("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n",
|
|
__func__);
|
|
if (subsys_get_restart_level(charm_subsys) == RESET_SOC)
|
|
pm8xxx_stay_on();
|
|
|
|
charm_disable_irqs();
|
|
gpio_set_value(AP2MDM_ERRFATAL, 1);
|
|
gpio_set_value(AP2MDM_WAKEUP, 1);
|
|
for (i = CHARM_MODEM_TIMEOUT; i > 0; i -= CHARM_MODEM_DELTA) {
|
|
pet_watchdog();
|
|
mdelay(CHARM_MODEM_DELTA);
|
|
if (gpio_get_value(MDM2AP_STATUS) == 0)
|
|
break;
|
|
}
|
|
if (i <= 0)
|
|
pr_err("%s: MDM2AP_STATUS never went low\n", __func__);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block charm_panic_blk = {
|
|
.notifier_call = charm_panic_prep,
|
|
};
|
|
|
|
static int first_boot = 1;
|
|
|
|
static long charm_modem_ioctl(struct file *filp, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
|
|
int status, ret = 0;
|
|
|
|
if (_IOC_TYPE(cmd) != CHARM_CODE) {
|
|
pr_err("%s: invalid ioctl code\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHARM_DBG("%s: Entering ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
|
|
switch (cmd) {
|
|
case WAKE_CHARM:
|
|
CHARM_DBG("%s: Powering on\n", __func__);
|
|
power_on_charm();
|
|
break;
|
|
case CHECK_FOR_BOOT:
|
|
if (gpio_get_value(MDM2AP_STATUS) == 0)
|
|
put_user(1, (unsigned long __user *) arg);
|
|
else
|
|
put_user(0, (unsigned long __user *) arg);
|
|
break;
|
|
case NORMAL_BOOT_DONE:
|
|
CHARM_DBG("%s: check if charm is booted up\n", __func__);
|
|
get_user(status, (unsigned long __user *) arg);
|
|
if (status)
|
|
charm_boot_status = -EIO;
|
|
else
|
|
charm_boot_status = 0;
|
|
charm_ready = 1;
|
|
|
|
gpio_set_value(AP2MDM_KPDPWR_N, 0);
|
|
if (!first_boot)
|
|
complete(&charm_boot);
|
|
else
|
|
first_boot = 0;
|
|
break;
|
|
case RAM_DUMP_DONE:
|
|
CHARM_DBG("%s: charm done collecting RAM dumps\n", __func__);
|
|
get_user(status, (unsigned long __user *) arg);
|
|
if (status)
|
|
charm_ram_dump_status = -EIO;
|
|
else
|
|
charm_ram_dump_status = 0;
|
|
complete(&charm_ram_dumps);
|
|
break;
|
|
case WAIT_FOR_RESTART:
|
|
CHARM_DBG("%s: wait for charm to need images reloaded\n",
|
|
__func__);
|
|
ret = wait_for_completion_interruptible(&charm_needs_reload);
|
|
if (!ret)
|
|
put_user(boot_type, (unsigned long __user *) arg);
|
|
INIT_COMPLETION(charm_needs_reload);
|
|
break;
|
|
default:
|
|
pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int charm_modem_open(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations charm_modem_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = charm_modem_open,
|
|
.unlocked_ioctl = charm_modem_ioctl,
|
|
};
|
|
|
|
|
|
struct miscdevice charm_modem_misc = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "mdm",
|
|
.fops = &charm_modem_fops
|
|
};
|
|
|
|
|
|
|
|
static void charm_status_fn(struct work_struct *work)
|
|
{
|
|
pr_info("Reseting the charm because status changed\n");
|
|
subsystem_restart_dev(charm_subsys);
|
|
}
|
|
|
|
static DECLARE_WORK(charm_status_work, charm_status_fn);
|
|
|
|
static void charm_fatal_fn(struct work_struct *work)
|
|
{
|
|
pr_info("Reseting the charm due to an errfatal\n");
|
|
if (subsys_get_restart_level(charm_subsys) == RESET_SOC)
|
|
pm8xxx_stay_on();
|
|
subsystem_restart_dev(charm_subsys);
|
|
}
|
|
|
|
static DECLARE_WORK(charm_fatal_work, charm_fatal_fn);
|
|
|
|
static irqreturn_t charm_errfatal(int irq, void *dev_id)
|
|
{
|
|
CHARM_DBG("%s: charm got errfatal interrupt\n", __func__);
|
|
if (charm_ready && (gpio_get_value(MDM2AP_STATUS) == 1)) {
|
|
CHARM_DBG("%s: scheduling work now\n", __func__);
|
|
queue_work(charm_queue, &charm_fatal_work);
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t charm_status_change(int irq, void *dev_id)
|
|
{
|
|
CHARM_DBG("%s: charm sent status change interrupt\n", __func__);
|
|
if ((gpio_get_value(MDM2AP_STATUS) == 0) && charm_ready) {
|
|
CHARM_DBG("%s: scheduling work now\n", __func__);
|
|
queue_work(charm_queue, &charm_status_work);
|
|
} else if (gpio_get_value(MDM2AP_STATUS) == 1) {
|
|
CHARM_DBG("%s: charm is now ready\n", __func__);
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int charm_debug_on_set(void *data, u64 val)
|
|
{
|
|
charm_debug_on = val;
|
|
return 0;
|
|
}
|
|
|
|
static int charm_debug_on_get(void *data, u64 *val)
|
|
{
|
|
*val = charm_debug_on;
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(charm_debug_on_fops,
|
|
charm_debug_on_get,
|
|
charm_debug_on_set, "%llu\n");
|
|
|
|
static int charm_debugfs_init(void)
|
|
{
|
|
struct dentry *dent;
|
|
|
|
dent = debugfs_create_dir("charm_dbg", 0);
|
|
if (IS_ERR(dent))
|
|
return PTR_ERR(dent);
|
|
|
|
debugfs_create_file("debug_on", 0644, dent, NULL,
|
|
&charm_debug_on_fops);
|
|
return 0;
|
|
}
|
|
|
|
static int gsbi9_uart_notifier_cb(struct notifier_block *this,
|
|
unsigned long code, void *_cmd)
|
|
{
|
|
switch (code) {
|
|
case SUBSYS_AFTER_SHUTDOWN:
|
|
platform_device_unregister(msm_device_uart_gsbi9);
|
|
msm_device_uart_gsbi9 = msm_add_gsbi9_uart();
|
|
if (IS_ERR(msm_device_uart_gsbi9))
|
|
pr_err("%s(): Failed to create uart gsbi9 device\n",
|
|
__func__);
|
|
default:
|
|
break;
|
|
}
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block gsbi9_nb = {
|
|
.notifier_call = gsbi9_uart_notifier_cb,
|
|
};
|
|
|
|
static int __init charm_modem_probe(struct platform_device *pdev)
|
|
{
|
|
int ret, irq;
|
|
struct charm_platform_data *d = pdev->dev.platform_data;
|
|
|
|
gpio_request(AP2MDM_STATUS, "AP2MDM_STATUS");
|
|
gpio_request(AP2MDM_ERRFATAL, "AP2MDM_ERRFATAL");
|
|
gpio_request(AP2MDM_KPDPWR_N, "AP2MDM_KPDPWR_N");
|
|
gpio_request(AP2MDM_PMIC_RESET_N, "AP2MDM_PMIC_RESET_N");
|
|
gpio_request(MDM2AP_STATUS, "MDM2AP_STATUS");
|
|
gpio_request(MDM2AP_ERRFATAL, "MDM2AP_ERRFATAL");
|
|
gpio_request(AP2MDM_WAKEUP, "AP2MDM_WAKEUP");
|
|
|
|
gpio_direction_output(AP2MDM_STATUS, 1);
|
|
gpio_direction_output(AP2MDM_ERRFATAL, 0);
|
|
gpio_direction_output(AP2MDM_WAKEUP, 0);
|
|
gpio_direction_input(MDM2AP_STATUS);
|
|
gpio_direction_input(MDM2AP_ERRFATAL);
|
|
|
|
power_on_charm = d->charm_modem_on;
|
|
power_down_charm = d->charm_modem_off;
|
|
|
|
charm_queue = create_singlethread_workqueue("charm_queue");
|
|
if (!charm_queue) {
|
|
pr_err("%s: could not create workqueue. All charm \
|
|
functionality will be disabled\n",
|
|
__func__);
|
|
ret = -ENOMEM;
|
|
goto fatal_err;
|
|
}
|
|
|
|
atomic_notifier_chain_register(&panic_notifier_list, &charm_panic_blk);
|
|
charm_debugfs_init();
|
|
|
|
charm_subsys = subsys_register(&charm_subsystem);
|
|
if (IS_ERR(charm_subsys)) {
|
|
ret = PTR_ERR(charm_subsys);
|
|
goto fatal_err;
|
|
}
|
|
subsys_default_online(charm_subsys);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
pr_err("%s: could not get MDM2AP_ERRFATAL IRQ resource. \
|
|
error=%d No IRQ will be generated on errfatal.",
|
|
__func__, irq);
|
|
goto errfatal_err;
|
|
}
|
|
|
|
ret = request_irq(irq, charm_errfatal,
|
|
IRQF_TRIGGER_RISING , "charm errfatal", NULL);
|
|
|
|
if (ret < 0) {
|
|
pr_err("%s: MDM2AP_ERRFATAL IRQ#%d request failed with error=%d\
|
|
. No IRQ will be generated on errfatal.",
|
|
__func__, irq, ret);
|
|
goto errfatal_err;
|
|
}
|
|
charm_errfatal_irq = irq;
|
|
|
|
errfatal_err:
|
|
|
|
irq = platform_get_irq(pdev, 1);
|
|
if (irq < 0) {
|
|
pr_err("%s: could not get MDM2AP_STATUS IRQ resource. \
|
|
error=%d No IRQ will be generated on status change.",
|
|
__func__, irq);
|
|
goto status_err;
|
|
}
|
|
|
|
ret = request_threaded_irq(irq, NULL, charm_status_change,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
|
"charm status", NULL);
|
|
|
|
if (ret < 0) {
|
|
pr_err("%s: MDM2AP_STATUS IRQ#%d request failed with error=%d\
|
|
. No IRQ will be generated on status change.",
|
|
__func__, irq, ret);
|
|
goto status_err;
|
|
}
|
|
charm_status_irq = irq;
|
|
|
|
status_err:
|
|
subsys_notif_register_notifier("external_modem", &gsbi9_nb);
|
|
|
|
pr_info("%s: Registering charm modem\n", __func__);
|
|
|
|
return misc_register(&charm_modem_misc);
|
|
|
|
fatal_err:
|
|
gpio_free(AP2MDM_STATUS);
|
|
gpio_free(AP2MDM_ERRFATAL);
|
|
gpio_free(AP2MDM_KPDPWR_N);
|
|
gpio_free(AP2MDM_PMIC_RESET_N);
|
|
gpio_free(MDM2AP_STATUS);
|
|
gpio_free(MDM2AP_ERRFATAL);
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
static int __devexit charm_modem_remove(struct platform_device *pdev)
|
|
{
|
|
gpio_free(AP2MDM_STATUS);
|
|
gpio_free(AP2MDM_ERRFATAL);
|
|
gpio_free(AP2MDM_KPDPWR_N);
|
|
gpio_free(AP2MDM_PMIC_RESET_N);
|
|
gpio_free(MDM2AP_STATUS);
|
|
gpio_free(MDM2AP_ERRFATAL);
|
|
|
|
return misc_deregister(&charm_modem_misc);
|
|
}
|
|
|
|
static void charm_modem_shutdown(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
|
|
CHARM_DBG("%s: setting AP2MDM_STATUS low for a graceful restart\n",
|
|
__func__);
|
|
|
|
charm_disable_irqs();
|
|
|
|
gpio_set_value(AP2MDM_STATUS, 0);
|
|
gpio_set_value(AP2MDM_WAKEUP, 1);
|
|
|
|
for (i = CHARM_MODEM_TIMEOUT; i > 0; i -= CHARM_MODEM_DELTA) {
|
|
pet_watchdog();
|
|
msleep(CHARM_MODEM_DELTA);
|
|
if (gpio_get_value(MDM2AP_STATUS) == 0)
|
|
break;
|
|
}
|
|
|
|
if (i <= 0) {
|
|
pr_err("%s: MDM2AP_STATUS never went low.\n",
|
|
__func__);
|
|
gpio_direction_output(AP2MDM_PMIC_RESET_N, 1);
|
|
for (i = CHARM_HOLD_TIME; i > 0; i -= CHARM_MODEM_DELTA) {
|
|
pet_watchdog();
|
|
msleep(CHARM_MODEM_DELTA);
|
|
}
|
|
gpio_direction_output(AP2MDM_PMIC_RESET_N, 0);
|
|
}
|
|
gpio_set_value(AP2MDM_WAKEUP, 0);
|
|
}
|
|
|
|
static struct platform_driver charm_modem_driver = {
|
|
.remove = charm_modem_remove,
|
|
.shutdown = charm_modem_shutdown,
|
|
.driver = {
|
|
.name = "charm_modem",
|
|
.owner = THIS_MODULE
|
|
},
|
|
};
|
|
|
|
static int __init charm_modem_init(void)
|
|
{
|
|
return platform_driver_probe(&charm_modem_driver, charm_modem_probe);
|
|
}
|
|
|
|
static void __exit charm_modem_exit(void)
|
|
{
|
|
platform_driver_unregister(&charm_modem_driver);
|
|
}
|
|
|
|
module_init(charm_modem_init);
|
|
module_exit(charm_modem_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("msm8660 charm modem driver");
|
|
MODULE_VERSION("1.0");
|
|
MODULE_ALIAS("charm_modem");
|