aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-exynos/mdm_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-exynos/mdm_common.c')
-rw-r--r--arch/arm/mach-exynos/mdm_common.c567
1 files changed, 567 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/mdm_common.c b/arch/arm/mach-exynos/mdm_common.c
new file mode 100644
index 0000000..30b5f75
--- /dev/null
+++ b/arch/arm/mach-exynos/mdm_common.c
@@ -0,0 +1,567 @@
+/* Copyright (c) 2011-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/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 <linux/clk.h>
+#ifndef CONFIG_ARCH_EXYNOS
+#include <linux/mfd/pmic8058.h>
+#endif
+#include <asm/mach-types.h>
+#include <asm/uaccess.h>
+#include <mach/mdm2.h>
+#include <mach/restart.h>
+#include <mach/subsystem_notif.h>
+#include <mach/subsystem_restart.h>
+#include <linux/msm_charm.h>
+#ifndef CONFIG_ARCH_EXYNOS
+#include "msm_watchdog.h"
+#endif
+#include "mdm_private.h"
+
+#ifdef CONFIG_ARCH_EXYNOS
+#include <linux/interrupt.h>
+#include <plat/gpio-cfg.h>
+#endif
+
+#define MDM_MODEM_TIMEOUT 6000
+#define MDM_MODEM_DELTA 100
+#define MDM_BOOT_TIMEOUT 60000L
+#define MDM_RDUMP_TIMEOUT 60000L
+
+static int mdm_debug_on;
+static struct workqueue_struct *mdm_queue;
+
+#define EXTERNAL_MODEM "external_modem"
+
+static struct mdm_modem_drv *mdm_drv;
+
+DECLARE_COMPLETION(mdm_needs_reload);
+DECLARE_COMPLETION(mdm_boot);
+DECLARE_COMPLETION(mdm_ram_dumps);
+
+static int first_boot = 1;
+
+long mdm_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;
+ }
+
+ pr_debug("%s: Entering ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
+ switch (cmd) {
+ case WAKE_CHARM:
+ pr_info("%s: Powering on mdm\n", __func__);
+ mdm_drv->ops->power_on_mdm_cb(mdm_drv);
+ break;
+ case CHECK_FOR_BOOT:
+ if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
+ put_user(1, (unsigned long __user *) arg);
+ else
+ put_user(0, (unsigned long __user *) arg);
+ break;
+ case NORMAL_BOOT_DONE:
+ pr_info("%s: check if mdm is booted up\n", __func__);
+ get_user(status, (unsigned long __user *) arg);
+ if (status) {
+ pr_debug("%s: normal boot failed\n", __func__);
+ mdm_drv->mdm_boot_status = -EIO;
+ } else {
+ pr_info("%s: normal boot done\n", __func__);
+ mdm_drv->mdm_boot_status = 0;
+ }
+ mdm_drv->mdm_ready = 1;
+
+ if (mdm_drv->ops->normal_boot_done_cb != NULL)
+ mdm_drv->ops->normal_boot_done_cb(mdm_drv);
+
+ if (!first_boot)
+ complete(&mdm_boot);
+ else
+ first_boot = 0;
+ break;
+ case RAM_DUMP_DONE:
+ pr_info("%s: mdm done collecting RAM dumps\n", __func__);
+ get_user(status, (unsigned long __user *) arg);
+ if (status)
+ mdm_drv->mdm_ram_dump_status = -EIO;
+ else {
+ pr_info("%s: ramdump collection completed\n", __func__);
+ mdm_drv->mdm_ram_dump_status = 0;
+ }
+ complete(&mdm_ram_dumps);
+ break;
+ case WAIT_FOR_RESTART:
+ pr_info("%s: wait for mdm to need images reloaded\n",
+ __func__);
+ ret = wait_for_completion_interruptible(&mdm_needs_reload);
+ if (!ret)
+ put_user(mdm_drv->boot_type,
+ (unsigned long __user *) arg);
+ INIT_COMPLETION(mdm_needs_reload);
+ break;
+ default:
+ pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static void mdm_fatal_fn(struct work_struct *work)
+{
+ pr_info("%s: Reseting the mdm due to an errfatal\n", __func__);
+ subsystem_restart(EXTERNAL_MODEM);
+}
+
+static DECLARE_WORK(mdm_fatal_work, mdm_fatal_fn);
+
+static void mdm_status_fn(struct work_struct *work)
+{
+ int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
+
+ if (!mdm_drv->mdm_ready)
+ return;
+
+ mdm_drv->ops->status_cb(mdm_drv, value);
+
+ pr_err("%s: status:%d\n", __func__, value);
+
+ if ((value == 0)) {
+ pr_info("%s: unexpected reset external modem\n", __func__);
+ subsystem_restart(EXTERNAL_MODEM);
+ } else if (value == 1) {
+ pr_info("%s: status = 1: mdm is now ready\n", __func__);
+ }
+}
+
+static DECLARE_WORK(mdm_status_work, mdm_status_fn);
+
+static void mdm_disable_irqs(void)
+{
+ disable_irq_nosync(mdm_drv->mdm_errfatal_irq);
+ disable_irq_nosync(mdm_drv->mdm_status_irq);
+
+}
+
+static irqreturn_t mdm_errfatal(int irq, void *dev_id)
+{
+ pr_debug("%s: mdm got errfatal interrupt\n", __func__);
+ if (mdm_drv->mdm_ready &&
+ (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 1)) {
+ pr_debug("%s: scheduling work now\n", __func__);
+ queue_work(mdm_queue, &mdm_fatal_work);
+ }
+ return IRQ_HANDLED;
+}
+
+static int mdm_modem_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct file_operations mdm_modem_fops = {
+ .owner = THIS_MODULE,
+ .open = mdm_modem_open,
+ .unlocked_ioctl = mdm_modem_ioctl,
+};
+
+
+static struct miscdevice mdm_modem_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "mdm",
+ .fops = &mdm_modem_fops
+};
+
+static int mdm_panic_prep(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ int i;
+
+ pr_debug("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n",
+ __func__);
+ mdm_disable_irqs();
+ gpio_set_value(mdm_drv->ap2mdm_errfatal_gpio, 1);
+
+ for (i = MDM_MODEM_TIMEOUT; i > 0; i -= MDM_MODEM_DELTA) {
+ /* pet_watchdog(); */
+ mdelay(MDM_MODEM_DELTA);
+ if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
+ break;
+ }
+ if (i <= 0)
+ pr_err("%s: MDM2AP_STATUS never went low\n", __func__);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block mdm_panic_blk = {
+ .notifier_call = mdm_panic_prep,
+};
+
+static irqreturn_t mdm_status_change(int irq, void *dev_id)
+{
+ int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
+ pr_err("%s: mdm sent status change interrupt : %d\n", __func__, value);
+
+ queue_work(mdm_queue, &mdm_status_work);
+
+ return IRQ_HANDLED;
+}
+
+static int mdm_subsys_shutdown(const struct subsys_data *crashed_subsys)
+{
+ pr_info("%s\n", __func__);
+ mdm_drv->mdm_ready = 0;
+ gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 1);
+ if (mdm_drv->pdata->ramdump_delay_ms > 0) {
+ /* Wait for the external modem to complete
+ * its preparation for ramdumps.
+ */
+ msleep(mdm_drv->pdata->ramdump_delay_ms);
+ }
+ mdm_drv->ops->power_down_mdm_cb(mdm_drv);
+ return 0;
+}
+
+static int mdm_subsys_powerup(const struct subsys_data *crashed_subsys)
+{
+ pr_info("%s\n", __func__);
+ gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0);
+ gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1);
+ mdm_drv->ops->power_on_mdm_cb(mdm_drv);
+ mdm_drv->boot_type = CHARM_NORMAL_BOOT;
+ complete(&mdm_needs_reload);
+ if (!wait_for_completion_timeout(&mdm_boot,
+ msecs_to_jiffies(MDM_BOOT_TIMEOUT))) {
+ mdm_drv->mdm_boot_status = -ETIMEDOUT;
+ pr_info("%s: mdm modem restart timed out.\n", __func__);
+ } else
+ pr_info("%s: mdm modem has been restarted\n", __func__);
+ INIT_COMPLETION(mdm_boot);
+ return mdm_drv->mdm_boot_status;
+}
+
+static int mdm_subsys_ramdumps(int want_dumps,
+ const struct subsys_data *crashed_subsys)
+{
+ pr_info("%s\n", __func__);
+ mdm_drv->mdm_ram_dump_status = 0;
+ if (want_dumps) {
+ mdm_drv->boot_type = CHARM_RAM_DUMPS;
+ complete(&mdm_needs_reload);
+ if (!wait_for_completion_timeout(&mdm_ram_dumps,
+ msecs_to_jiffies(MDM_RDUMP_TIMEOUT))) {
+ mdm_drv->mdm_ram_dump_status = -ETIMEDOUT;
+ pr_info("%s: mdm modem ramdumps timed out.\n",
+ __func__);
+ } else
+ pr_info("%s: mdm modem ramdumps completed.\n",
+ __func__);
+ INIT_COMPLETION(mdm_ram_dumps);
+ gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 1);
+ mdm_drv->ops->power_down_mdm_cb(mdm_drv);
+ }
+ return mdm_drv->mdm_ram_dump_status;
+}
+
+static struct subsys_data mdm_subsystem = {
+ .shutdown = mdm_subsys_shutdown,
+ .ramdump = mdm_subsys_ramdumps,
+ .powerup = mdm_subsys_powerup,
+ .name = EXTERNAL_MODEM,
+};
+
+static int mdm_debug_on_set(void *data, u64 val)
+{
+ mdm_debug_on = val;
+ if (mdm_drv->ops->debug_state_changed_cb)
+ mdm_drv->ops->debug_state_changed_cb(mdm_debug_on);
+ return 0;
+}
+
+static int mdm_debug_on_get(void *data, u64 *val)
+{
+ *val = mdm_debug_on;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(mdm_debug_on_fops,
+ mdm_debug_on_get,
+ mdm_debug_on_set, "%llu\n");
+
+static int mdm_debugfs_init(void)
+{
+ struct dentry *dent;
+
+ dent = debugfs_create_dir("mdm_dbg", 0);
+ if (IS_ERR(dent))
+ return PTR_ERR(dent);
+
+ debugfs_create_file("debug_on", 0644, dent, NULL,
+ &mdm_debug_on_fops);
+ return 0;
+}
+
+static void mdm_modem_initialize_data(struct platform_device *pdev,
+ struct mdm_ops *mdm_ops)
+{
+ struct resource *pres;
+
+ /* MDM2AP_ERRFATAL */
+ pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "MDM2AP_ERRFATAL");
+ if (pres)
+ mdm_drv->mdm2ap_errfatal_gpio = pres->start;
+
+ /* AP2MDM_ERRFATAL */
+ pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "AP2MDM_ERRFATAL");
+ if (pres)
+ mdm_drv->ap2mdm_errfatal_gpio = pres->start;
+
+ /* MDM2AP_STATUS */
+ pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "MDM2AP_STATUS");
+ if (pres)
+ mdm_drv->mdm2ap_status_gpio = pres->start;
+
+ /* AP2MDM_STATUS */
+ pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "AP2MDM_STATUS");
+ if (pres)
+ mdm_drv->ap2mdm_status_gpio = pres->start;
+
+ /* MDM2AP_WAKEUP */
+ pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "MDM2AP_WAKEUP");
+ if (pres)
+ mdm_drv->mdm2ap_wakeup_gpio = pres->start;
+
+ /* AP2MDM_WAKEUP */
+ pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "AP2MDM_WAKEUP");
+ if (pres)
+ mdm_drv->ap2mdm_wakeup_gpio = pres->start;
+
+ /* AP2MDM_PMIC_RESET_N */
+ pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "AP2MDM_PMIC_RESET_N");
+ if (pres)
+ mdm_drv->ap2mdm_pmic_reset_n_gpio = pres->start;
+
+ /* AP2MDM_KPDPWR_N */
+ pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "AP2MDM_KPDPWR_N");
+ if (pres)
+ mdm_drv->ap2mdm_kpdpwr_n_gpio = pres->start;
+
+ mdm_drv->boot_type = CHARM_NORMAL_BOOT;
+
+ mdm_drv->ops = mdm_ops;
+ mdm_drv->pdata = pdev->dev.platform_data;
+}
+
+int mdm_common_create(struct platform_device *pdev,
+ struct mdm_ops *p_mdm_cb)
+{
+ int ret = -1, irq;
+ pr_err("%s\n", __func__);
+
+ mdm_drv = kzalloc(sizeof(struct mdm_modem_drv), GFP_KERNEL);
+ if (mdm_drv == NULL) {
+ pr_err("%s: kzalloc fail.\n", __func__);
+ goto alloc_err;
+ }
+
+ mdm_modem_initialize_data(pdev, p_mdm_cb);
+ if (mdm_drv->ops->debug_state_changed_cb)
+ mdm_drv->ops->debug_state_changed_cb(mdm_debug_on);
+
+ gpio_request(mdm_drv->ap2mdm_status_gpio, "AP2MDM_STATUS");
+ gpio_request(mdm_drv->ap2mdm_errfatal_gpio, "AP2MDM_ERRFATAL");
+ gpio_request(mdm_drv->ap2mdm_kpdpwr_n_gpio, "AP2MDM_KPDPWR_N");
+ gpio_request(mdm_drv->ap2mdm_pmic_reset_n_gpio, "AP2MDM_PMIC_RESET_N");
+ gpio_request(mdm_drv->mdm2ap_status_gpio, "MDM2AP_STATUS");
+ gpio_request(mdm_drv->mdm2ap_errfatal_gpio, "MDM2AP_ERRFATAL");
+
+ if (mdm_drv->ap2mdm_wakeup_gpio > 0)
+ gpio_request(mdm_drv->ap2mdm_wakeup_gpio, "AP2MDM_WAKEUP");
+
+#ifdef CONFIG_ARCH_EXYNOS
+ gpio_set_value(mdm_drv->ap2mdm_status_gpio, 1);
+ s3c_gpio_cfgpin(mdm_drv->ap2mdm_status_gpio, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(mdm_drv->ap2mdm_status_gpio, S3C_GPIO_PULL_UP);
+#endif
+ gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1);
+ pr_err("%s> : right after ap2mdm_status = %d\n", __func__,
+ gpio_get_value(mdm_drv->ap2mdm_status_gpio));
+
+#ifdef CONFIG_ARCH_EXYNOS
+ gpio_set_value(mdm_drv->ap2mdm_errfatal_gpio, 0);
+ s3c_gpio_cfgpin(mdm_drv->ap2mdm_errfatal_gpio, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(mdm_drv->ap2mdm_errfatal_gpio, S3C_GPIO_PULL_DOWN);
+#endif
+ gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0);
+ pr_err("%s>> : right after ap2mdm_status = %d\n", __func__,
+ gpio_get_value(mdm_drv->ap2mdm_status_gpio));
+
+ if (mdm_drv->ap2mdm_wakeup_gpio > 0)
+ gpio_direction_output(mdm_drv->ap2mdm_wakeup_gpio, 0);
+
+ gpio_direction_input(mdm_drv->mdm2ap_status_gpio);
+ gpio_direction_input(mdm_drv->mdm2ap_errfatal_gpio);
+
+ mdm_queue = create_singlethread_workqueue("mdm_queue");
+ if (!mdm_queue) {
+ pr_err("%s: could not create workqueue. All mdm "
+ "functionality will be disabled\n",
+ __func__);
+ ret = -ENOMEM;
+ goto fatal_err;
+ }
+
+ atomic_notifier_chain_register(&panic_notifier_list, &mdm_panic_blk);
+ mdm_debugfs_init();
+
+ /* Register subsystem handlers */
+ ssr_register_subsystem(&mdm_subsystem);
+
+ /* ERR_FATAL irq. */
+#ifdef CONFIG_ARCH_EXYNOS
+ irq = gpio_to_irq(mdm_drv->mdm2ap_errfatal_gpio);
+#else
+ irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_errfatal_gpio);
+#endif
+ 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, mdm_errfatal,
+ IRQF_TRIGGER_RISING , "mdm 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;
+ }
+ mdm_drv->mdm_errfatal_irq = irq;
+
+errfatal_err:
+
+ /* status irq */
+#ifdef CONFIG_ARCH_EXYNOS
+ ret = s5p_register_gpio_interrupt(mdm_drv->mdm2ap_status_gpio);
+ if (ret)
+ pr_err("%s: register MDM2AP_STATUS ret = %d\n", __func__, ret);
+ irq = gpio_to_irq(mdm_drv->mdm2ap_status_gpio);
+#else
+ irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_status_gpio);
+#endif
+ 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, mdm_status_change,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
+ "mdm status", mdm_drv);
+
+ 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;
+ }
+ mdm_drv->mdm_status_irq = irq;
+
+status_err:
+ /* Perform early powerup of the external modem in order to
+ * allow tabla devices to be found.
+ */
+ mdm_drv->ops->power_on_mdm_cb(mdm_drv);
+ pr_err("%s : ap2mdm_status = %d\n", __func__,
+ gpio_get_value(mdm_drv->ap2mdm_status_gpio));
+
+ pr_info("%s: Registering mdm modem\n", __func__);
+ return misc_register(&mdm_modem_misc);
+
+fatal_err:
+ gpio_free(mdm_drv->ap2mdm_status_gpio);
+ gpio_free(mdm_drv->ap2mdm_errfatal_gpio);
+ gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio);
+ gpio_free(mdm_drv->ap2mdm_pmic_reset_n_gpio);
+ gpio_free(mdm_drv->mdm2ap_status_gpio);
+ gpio_free(mdm_drv->mdm2ap_errfatal_gpio);
+
+ if (mdm_drv->ap2mdm_wakeup_gpio > 0)
+ gpio_free(mdm_drv->ap2mdm_wakeup_gpio);
+
+ kfree(mdm_drv);
+ ret = -ENODEV;
+
+alloc_err:
+ return ret;
+}
+
+int mdm_common_modem_remove(struct platform_device *pdev)
+{
+ int ret;
+
+ gpio_free(mdm_drv->ap2mdm_status_gpio);
+ gpio_free(mdm_drv->ap2mdm_errfatal_gpio);
+ gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio);
+ gpio_free(mdm_drv->ap2mdm_pmic_reset_n_gpio);
+ gpio_free(mdm_drv->mdm2ap_status_gpio);
+ gpio_free(mdm_drv->mdm2ap_errfatal_gpio);
+
+ if (mdm_drv->ap2mdm_wakeup_gpio > 0)
+ gpio_free(mdm_drv->ap2mdm_wakeup_gpio);
+
+ kfree(mdm_drv);
+
+ ret = misc_deregister(&mdm_modem_misc);
+ return ret;
+}
+
+void mdm_common_modem_shutdown(struct platform_device *pdev)
+{
+ mdm_disable_irqs();
+
+ mdm_drv->ops->power_down_mdm_cb(mdm_drv);
+}
+