/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");