Initial Contribution

msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142

Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/mdm.c b/arch/arm/mach-msm/mdm.c
new file mode 100644
index 0000000..47e88ee
--- /dev/null
+++ b/arch/arm/mach-msm/mdm.c
@@ -0,0 +1,464 @@
+/* Copyright (c) 2010-2011, 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>
+#include <asm/mach-types.h>
+#include <asm/uaccess.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"
+#include "clock.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 void charm_enable_irqs(void)
+{
+	enable_irq(charm_errfatal_irq);
+	enable_irq(charm_status_irq);
+}
+
+static int charm_subsys_shutdown(const struct subsys_data *crashed_subsys)
+{
+	charm_disable_irqs();
+	power_down_charm();
+	charm_ready = 0;
+	return 0;
+}
+
+static int charm_subsys_powerup(const struct subsys_data *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);
+	charm_enable_irqs();
+	return charm_boot_status;
+}
+
+static int charm_subsys_ramdumps(int want_dumps,
+				const struct subsys_data *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_data 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)
+{
+	CHARM_DBG("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n",
+			 __func__);
+	charm_disable_irqs();
+	gpio_set_value(AP2MDM_ERRFATAL, 1);
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block charm_panic_blk = {
+	.notifier_call  = charm_panic_prep,
+};
+
+
+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;
+		complete(&charm_boot);
+		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("external_modem");
+}
+
+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");
+	subsystem_restart("external_modem");
+}
+
+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) {
+		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__);
+		charm_ready = 1;
+	}
+	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_direction_output(AP2MDM_STATUS, 1);
+	gpio_direction_output(AP2MDM_ERRFATAL, 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();
+
+	ssr_register_subsystem(&charm_subsystem);
+
+	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);
+
+	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);
+	}
+}
+
+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");