/* Copyright (c) 2011-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 #include #include #include "msm_watchdog.h" #include "mdm_private.h" #include "sysmon.h" #define MDM_MODEM_TIMEOUT 6000 #define MDM_MODEM_DELTA 100 #define MDM_BOOT_TIMEOUT 60000L #define MDM_RDUMP_TIMEOUT 120000L #define MDM2AP_STATUS_TIMEOUT_MS 60000L /* Allow a maximum device id of this many digits */ #define MAX_DEVICE_DIGITS 10 #define EXTERNAL_MODEM "external_modem" #define SUBSYS_NAME_LENGTH \ (sizeof(EXTERNAL_MODEM) + MAX_DEVICE_DIGITS) #define DEVICE_BASE_NAME "mdm" #define DEVICE_NAME_LENGTH \ (sizeof(DEVICE_BASE_NAME) + MAX_DEVICE_DIGITS) #define RD_BUF_SIZE 100 #define SFR_MAX_RETRIES 10 #define SFR_RETRY_INTERVAL 1000 enum gpio_update_config { GPIO_UPDATE_BOOTING_CONFIG = 1, GPIO_UPDATE_RUNNING_CONFIG, }; struct mdm_device { struct list_head link; struct mdm_modem_drv mdm_data; int mdm2ap_status_valid_old_config; struct gpiomux_setting mdm2ap_status_old_config; int first_boot; struct workqueue_struct *mdm_queue; struct workqueue_struct *mdm_sfr_queue; unsigned int dump_timeout_ms; char subsys_name[SUBSYS_NAME_LENGTH]; struct subsys_desc mdm_subsys; struct subsys_device *mdm_subsys_dev; char device_name[DEVICE_NAME_LENGTH]; struct miscdevice misc_device; struct completion mdm_needs_reload; struct completion mdm_boot; struct completion mdm_ram_dumps; int mdm_errfatal_irq; int mdm_status_irq; int mdm_pblrdy_irq; struct delayed_work mdm2ap_status_check_work; struct work_struct mdm_status_work; struct work_struct sfr_reason_work; struct notifier_block mdm_panic_blk; int ssr_started_internally; }; static struct list_head mdm_devices; static DEFINE_SPINLOCK(mdm_devices_lock); static int ssr_count; static DEFINE_SPINLOCK(ssr_lock); static unsigned int mdm_debug_mask; int vddmin_gpios_sent; static struct mdm_ops *mdm_ops; static void mdm_device_list_add(struct mdm_device *mdev) { unsigned long flags; spin_lock_irqsave(&mdm_devices_lock, flags); list_add_tail(&mdev->link, &mdm_devices); spin_unlock_irqrestore(&mdm_devices_lock, flags); } static void mdm_device_list_remove(struct mdm_device *mdev) { unsigned long flags; struct mdm_device *lmdev, *tmp; spin_lock_irqsave(&mdm_devices_lock, flags); list_for_each_entry_safe(lmdev, tmp, &mdm_devices, link) { if (mdev && mdev == lmdev) { pr_debug("%s: removing device id %d\n", __func__, mdev->mdm_data.device_id); list_del(&mdev->link); break; } } spin_unlock_irqrestore(&mdm_devices_lock, flags); } /* If the platform's cascading_ssr flag is set, the subsystem * restart module will restart the other modems so stop * monitoring them as well. * This function can be called from interrupt context. */ static void mdm_start_ssr(struct mdm_device *mdev) { unsigned long flags; int start_ssr = 1; spin_lock_irqsave(&ssr_lock, flags); if (mdev->mdm_data.pdata->cascading_ssr && ssr_count > 0) { start_ssr = 0; } else { ssr_count++; mdev->ssr_started_internally = 1; } spin_unlock_irqrestore(&ssr_lock, flags); if (start_ssr) { atomic_set(&mdev->mdm_data.mdm_ready, 0); pr_debug("%s: Resetting mdm id %d due to mdm error\n", __func__, mdev->mdm_data.device_id); subsystem_restart_dev(mdev->mdm_subsys_dev); } else { pr_debug("%s: Another modem is already in SSR\n", __func__); } } /* Increment the reference count to handle the case where * subsystem restart is initiated by the SSR service. */ static void mdm_ssr_started(struct mdm_device *mdev) { unsigned long flags; spin_lock_irqsave(&ssr_lock, flags); ssr_count++; atomic_set(&mdev->mdm_data.mdm_ready, 0); spin_unlock_irqrestore(&ssr_lock, flags); } /* mdm_ssr_completed assumes that mdm_ssr_started has previously * been called. */ static void mdm_ssr_completed(struct mdm_device *mdev) { unsigned long flags; spin_lock_irqsave(&ssr_lock, flags); ssr_count--; if (mdev->ssr_started_internally) { mdev->ssr_started_internally = 0; ssr_count--; } if (ssr_count < 0) { pr_err("%s: ssr_count = %d\n", __func__, ssr_count); panic("%s: ssr_count = %d < 0\n", __func__, ssr_count); } spin_unlock_irqrestore(&ssr_lock, flags); } static irqreturn_t mdm_vddmin_change(int irq, void *dev_id) { struct mdm_device *mdev = (struct mdm_device *)dev_id; struct mdm_vddmin_resource *vddmin_res; int value; if (!mdev) goto handled; vddmin_res = mdev->mdm_data.pdata->vddmin_resource; if (!vddmin_res) goto handled; value = gpio_get_value( vddmin_res->mdm2ap_vddmin_gpio); if (value == 0) pr_debug("External Modem id %d entered Vddmin\n", mdev->mdm_data.device_id); else pr_debug("External Modem id %d exited Vddmin\n", mdev->mdm_data.device_id); handled: return IRQ_HANDLED; } /* The vddmin_res resource may not be supported by some platforms. */ static void mdm_setup_vddmin_gpios(void) { unsigned long flags; struct msm_rpm_iv_pair req; struct mdm_device *mdev; struct mdm_vddmin_resource *vddmin_res; int irq, ret; spin_lock_irqsave(&mdm_devices_lock, flags); list_for_each_entry(mdev, &mdm_devices, link) { vddmin_res = mdev->mdm_data.pdata->vddmin_resource; if (!vddmin_res) continue; pr_debug("Enabling vddmin logging on modem id %d\n", mdev->mdm_data.device_id); req.id = vddmin_res->rpm_id; req.value = ((uint32_t)vddmin_res->ap2mdm_vddmin_gpio & 0x0000FFFF) << 16; req.value |= ((uint32_t)vddmin_res->modes & 0x000000FF) << 8; req.value |= (uint32_t)vddmin_res->drive_strength & 0x000000FF; msm_rpm_set(MSM_RPM_CTX_SET_0, &req, 1); /* Start monitoring low power gpio from mdm */ irq = gpio_to_irq(vddmin_res->mdm2ap_vddmin_gpio); if (irq < 0) pr_err("%s: could not get LPM POWER IRQ resource mdm id %d.\n", __func__, mdev->mdm_data.device_id); else { ret = request_threaded_irq(irq, NULL, mdm_vddmin_change, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "mdm lpm", mdev); if (ret < 0) pr_err("%s: MDM LPM IRQ#%d request failed with error=%d", __func__, irq, ret); } } spin_unlock_irqrestore(&mdm_devices_lock, flags); return; } static void mdm_restart_reason_fn(struct work_struct *work) { int ret, ntries = 0; char sfr_buf[RD_BUF_SIZE]; struct mdm_platform_data *pdata; struct mdm_device *mdev = container_of(work, struct mdm_device, sfr_reason_work); pdata = mdev->mdm_data.pdata; if (pdata->sysmon_subsys_id_valid) { do { ret = sysmon_get_reason(pdata->sysmon_subsys_id, sfr_buf, sizeof(sfr_buf)); if (!ret) { pr_err("mdm restart reason: %s\n", sfr_buf); return; } /* Wait for the modem to be fully booted after a * subsystem restart. This may take several seconds. */ msleep(SFR_RETRY_INTERVAL); } while (++ntries < SFR_MAX_RETRIES); pr_debug("%s: Error retrieving restart reason: %d\n", __func__, ret); } } static void mdm2ap_status_check(struct work_struct *work) { struct mdm_device *mdev = container_of(work, struct mdm_device, mdm2ap_status_check_work.work); struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; /* * If the mdm modem did not pull the MDM2AP_STATUS gpio * high then call subsystem_restart. */ if (!mdm_drv->disable_status_check) { if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0) { pr_debug("%s: MDM2AP_STATUS did not go high on mdm id %d\n", __func__, mdev->mdm_data.device_id); mdm_start_ssr(mdev); } } } static void mdm_update_gpio_configs(struct mdm_device *mdev, enum gpio_update_config gpio_config) { struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; /* Some gpio configuration may need updating after modem bootup.*/ switch (gpio_config) { case GPIO_UPDATE_RUNNING_CONFIG: if (mdm_drv->pdata->mdm2ap_status_gpio_run_cfg) { if (msm_gpiomux_write(mdm_drv->mdm2ap_status_gpio, GPIOMUX_ACTIVE, mdm_drv->pdata->mdm2ap_status_gpio_run_cfg, &mdev->mdm2ap_status_old_config)) pr_err("%s: failed updating running gpio config mdm id %d\n", __func__, mdev->mdm_data.device_id); else mdev->mdm2ap_status_valid_old_config = 1; } break; case GPIO_UPDATE_BOOTING_CONFIG: if (mdev->mdm2ap_status_valid_old_config) { msm_gpiomux_write(mdm_drv->mdm2ap_status_gpio, GPIOMUX_ACTIVE, &mdev->mdm2ap_status_old_config, NULL); mdev->mdm2ap_status_valid_old_config = 0; } break; default: pr_err("%s: called with no config\n", __func__); break; } } static long mdm_modem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int status, ret = 0; struct mdm_device *mdev = filp->private_data; struct mdm_modem_drv *mdm_drv; if (_IOC_TYPE(cmd) != CHARM_CODE) { pr_err("%s: invalid ioctl code to mdm id %d\n", __func__, mdev->mdm_data.device_id); return -EINVAL; } mdm_drv = &mdev->mdm_data; pr_debug("%s: Entering ioctl cmd = %d, mdm id = %d\n", __func__, _IOC_NR(cmd), mdev->mdm_data.device_id); switch (cmd) { case WAKE_CHARM: pr_debug("%s: Powering on mdm id %d\n", __func__, mdev->mdm_data.device_id); mdm_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_debug("%s: check if mdm id %d is booted up\n", __func__, mdev->mdm_data.device_id); get_user(status, (unsigned long __user *) arg); if (status) { pr_debug("%s: normal boot of mdm id %d failed\n", __func__, mdev->mdm_data.device_id); mdm_drv->mdm_boot_status = -EIO; } else { pr_debug("%s: normal boot of mdm id %d done\n", __func__, mdev->mdm_data.device_id); mdm_drv->mdm_boot_status = 0; } atomic_set(&mdm_drv->mdm_ready, 1); if (mdm_ops->normal_boot_done_cb != NULL) mdm_ops->normal_boot_done_cb(mdm_drv); if (!mdev->first_boot) complete(&mdev->mdm_boot); else mdev->first_boot = 0; /* If successful, start a timer to check that the mdm2ap_status * gpio goes high. */ if (!status && gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0) schedule_delayed_work(&mdev->mdm2ap_status_check_work, msecs_to_jiffies(MDM2AP_STATUS_TIMEOUT_MS)); break; case RAM_DUMP_DONE: pr_debug("%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_debug("%s: ramdump collection completed\n", __func__); mdm_drv->mdm_ram_dump_status = 0; } complete(&mdev->mdm_ram_dumps); break; case WAIT_FOR_RESTART: pr_debug("%s: wait for mdm to need images reloaded\n", __func__); ret = wait_for_completion_interruptible( &mdev->mdm_needs_reload); if (!ret) put_user(mdm_drv->boot_type, (unsigned long __user *) arg); init_completion(&mdev->mdm_needs_reload); break; case GET_DLOAD_STATUS: pr_debug("getting status of mdm2ap_errfatal_gpio\n"); if (gpio_get_value(mdm_drv->mdm2ap_errfatal_gpio) == 1 && !atomic_read(&mdm_drv->mdm_ready)) put_user(1, (unsigned long __user *) arg); else put_user(0, (unsigned long __user *) arg); break; case IMAGE_UPGRADE: pr_debug("%s Image upgrade ioctl recieved\n", __func__); if (mdm_drv->pdata->image_upgrade_supported && mdm_ops->image_upgrade_cb) { get_user(status, (unsigned long __user *) arg); mdm_ops->image_upgrade_cb(mdm_drv, status); } else pr_debug("%s Image upgrade not supported\n", __func__); break; case SHUTDOWN_CHARM: if (!mdm_drv->pdata->send_shdn) break; atomic_set(&mdm_drv->mdm_ready, 0); if (mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG) pr_debug("Sending shutdown request to mdm\n"); ret = sysmon_send_shutdown(SYSMON_SS_EXT_MODEM); if (ret) pr_err("%s: Graceful shutdown of the external modem failed, ret = %d\n", __func__, ret); put_user(ret, (unsigned long __user *) arg); break; default: pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd)); ret = -EINVAL; break; } return ret; } static void mdm_status_fn(struct work_struct *work) { struct mdm_device *mdev = container_of(work, struct mdm_device, mdm_status_work); struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio); pr_debug("%s: status:%d\n", __func__, value); if (atomic_read(&mdm_drv->mdm_ready) && mdm_ops->status_cb) mdm_ops->status_cb(mdm_drv, value); /* Update gpio configuration to "running" config. */ mdm_update_gpio_configs(mdev, GPIO_UPDATE_RUNNING_CONFIG); } static void mdm_disable_irqs(struct mdm_device *mdev) { if (!mdev) return; disable_irq_nosync(mdev->mdm_errfatal_irq); disable_irq_nosync(mdev->mdm_status_irq); disable_irq_nosync(mdev->mdm_pblrdy_irq); } static irqreturn_t mdm_errfatal(int irq, void *dev_id) { struct mdm_modem_drv *mdm_drv; struct mdm_device *mdev = (struct mdm_device *)dev_id; if (!mdev) return IRQ_HANDLED; pr_debug("%s: mdm id %d sent errfatal interrupt\n", __func__, mdev->mdm_data.device_id); mdm_drv = &mdev->mdm_data; if (atomic_read(&mdm_drv->mdm_ready) && (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 1)) { pr_debug("%s: Received err fatal from mdm id %d\n", __func__, mdev->mdm_data.device_id); mdm_start_ssr(mdev); } return IRQ_HANDLED; } /* set the mdm_device as the file's private data */ static int mdm_modem_open(struct inode *inode, struct file *file) { struct miscdevice *misc = file->private_data; struct mdm_device *mdev = container_of(misc, struct mdm_device, misc_device); file->private_data = mdev; return 0; } static int mdm_panic_prep(struct notifier_block *this, unsigned long event, void *ptr) { int i; struct mdm_modem_drv *mdm_drv; struct mdm_device *mdev = container_of(this, struct mdm_device, mdm_panic_blk); mdm_drv = &mdev->mdm_data; pr_debug("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n", __func__); mdm_disable_irqs(mdev); 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__); /* Reset the modem so that it will go into download mode. */ if (mdm_drv && mdm_ops->atomic_reset_mdm_cb) mdm_ops->atomic_reset_mdm_cb(mdm_drv); } return NOTIFY_DONE; } static irqreturn_t mdm_status_change(int irq, void *dev_id) { struct mdm_modem_drv *mdm_drv; struct mdm_device *mdev = (struct mdm_device *)dev_id; int value; if (!mdev) return IRQ_HANDLED; mdm_drv = &mdev->mdm_data; value = gpio_get_value(mdm_drv->mdm2ap_status_gpio); if ((mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG) && (value == 0)) pr_debug("%s: mdm2ap_status went low\n", __func__); pr_debug("%s: mdm id %d sent status change interrupt\n", __func__, mdev->mdm_data.device_id); if (value == 0 && atomic_read(&mdm_drv->mdm_ready)) { pr_debug("%s: unexpected reset external modem id %d\n", __func__, mdev->mdm_data.device_id); mdm_drv->mdm_unexpected_reset_occurred = 1; mdm_start_ssr(mdev); } else if (value == 1) { cancel_delayed_work(&mdev->mdm2ap_status_check_work); pr_debug("%s: status = 1: mdm id %d is now ready\n", __func__, mdev->mdm_data.device_id); queue_work(mdev->mdm_queue, &mdev->mdm_status_work); } return IRQ_HANDLED; } static irqreturn_t mdm_pblrdy_change(int irq, void *dev_id) { struct mdm_modem_drv *mdm_drv; struct mdm_device *mdev = (struct mdm_device *)dev_id; if (!mdev) return IRQ_HANDLED; mdm_drv = &mdev->mdm_data; pr_debug("%s: mdm id %d: pbl ready:%d\n", __func__, mdev->mdm_data.device_id, gpio_get_value(mdm_drv->mdm2ap_pblrdy)); return IRQ_HANDLED; } static int mdm_subsys_shutdown(const struct subsys_desc *crashed_subsys) { struct mdm_device *mdev = container_of(crashed_subsys, struct mdm_device, mdm_subsys); struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; pr_debug("%s: ssr on modem id %d\n", __func__, mdev->mdm_data.device_id); mdm_ssr_started(mdev); cancel_delayed_work(&mdev->mdm2ap_status_check_work); 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); } if (!mdm_drv->mdm_unexpected_reset_occurred) { mdm_ops->reset_mdm_cb(mdm_drv); /* Update gpio configuration to "booting" config. */ mdm_update_gpio_configs(mdev, GPIO_UPDATE_BOOTING_CONFIG); } else { mdm_drv->mdm_unexpected_reset_occurred = 0; } return 0; } static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys) { struct mdm_device *mdev = container_of(crashed_subsys, struct mdm_device, mdm_subsys); struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; pr_debug("%s: ssr on modem id %d\n", __func__, mdev->mdm_data.device_id); gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0); gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1); if (mdm_drv->pdata->ps_hold_delay_ms > 0) msleep(mdm_drv->pdata->ps_hold_delay_ms); mdm_ops->power_on_mdm_cb(mdm_drv); mdm_drv->boot_type = CHARM_NORMAL_BOOT; mdm_ssr_completed(mdev); complete(&mdev->mdm_needs_reload); if (!wait_for_completion_timeout(&mdev->mdm_boot, msecs_to_jiffies(MDM_BOOT_TIMEOUT))) { mdm_drv->mdm_boot_status = -ETIMEDOUT; pr_debug("%s: mdm modem restart timed out.\n", __func__); } else { pr_debug("%s: id %d: mdm modem has been restarted\n", __func__, mdm_drv->device_id); /* Log the reason for the restart */ if (mdm_drv->pdata->sfr_query) queue_work(mdev->mdm_sfr_queue, &mdev->sfr_reason_work); } init_completion(&mdev->mdm_boot); return mdm_drv->mdm_boot_status; } static int mdm_subsys_ramdumps(int want_dumps, const struct subsys_desc *crashed_subsys) { struct mdm_device *mdev = container_of(crashed_subsys, struct mdm_device, mdm_subsys); struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; pr_debug("%s: ssr on modem id %d\n", __func__, mdev->mdm_data.device_id); mdm_drv->mdm_ram_dump_status = 0; cancel_delayed_work(&mdev->mdm2ap_status_check_work); if (want_dumps) { mdm_drv->boot_type = CHARM_RAM_DUMPS; complete(&mdev->mdm_needs_reload); if (!wait_for_completion_timeout(&mdev->mdm_ram_dumps, msecs_to_jiffies(mdev->dump_timeout_ms))) { mdm_drv->mdm_ram_dump_status = -ETIMEDOUT; mdm_ssr_completed(mdev); pr_err("%s: mdm modem ramdumps timed out.\n", __func__); } else pr_debug("%s: mdm modem ramdumps completed.\n", __func__); init_completion(&mdev->mdm_ram_dumps); if (!mdm_drv->pdata->no_powerdown_after_ramdumps) { mdm_ops->power_down_mdm_cb(mdm_drv); /* Update gpio configuration to "booting" config. */ mdm_update_gpio_configs(mdev, GPIO_UPDATE_BOOTING_CONFIG); } } return mdm_drv->mdm_ram_dump_status; } /* Once the gpios are sent to RPM and debugging * starts, there is no way to stop it without * rebooting the device. */ static int mdm_debug_mask_set(void *data, u64 val) { if (!vddmin_gpios_sent && (val & MDM_DEBUG_MASK_VDDMIN_SETUP)) { mdm_setup_vddmin_gpios(); vddmin_gpios_sent = 1; } mdm_debug_mask = val; if (mdm_ops->debug_state_changed_cb) mdm_ops->debug_state_changed_cb(mdm_debug_mask); return 0; } static int mdm_debug_mask_get(void *data, u64 *val) { *val = mdm_debug_mask; return 0; } DEFINE_SIMPLE_ATTRIBUTE(mdm_debug_mask_fops, mdm_debug_mask_get, mdm_debug_mask_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_mask", 0644, dent, NULL, &mdm_debug_mask_fops); return 0; } static const struct file_operations mdm_modem_fops = { .owner = THIS_MODULE, .open = mdm_modem_open, .unlocked_ioctl = mdm_modem_ioctl, }; static void mdm_modem_initialize_data(struct platform_device *pdev, struct mdm_device *mdev) { struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; struct resource *pres; mdm_drv->pdata = pdev->dev.platform_data; if (pdev->id < 0) mdm_drv->device_id = 0; else mdm_drv->device_id = pdev->id; memset((void *)&mdev->mdm_subsys, 0, sizeof(struct subsys_desc)); if (mdev->mdm_data.device_id <= 0) snprintf(mdev->subsys_name, sizeof(mdev->subsys_name), "%s", EXTERNAL_MODEM); else snprintf(mdev->subsys_name, sizeof(mdev->subsys_name), "%s.%d", EXTERNAL_MODEM, mdev->mdm_data.device_id); mdev->mdm_subsys.shutdown = mdm_subsys_shutdown; mdev->mdm_subsys.ramdump = mdm_subsys_ramdumps; mdev->mdm_subsys.powerup = mdm_subsys_powerup; mdev->mdm_subsys.name = mdev->subsys_name; memset((void *)&mdev->misc_device, 0, sizeof(struct miscdevice)); if (mdev->mdm_data.device_id <= 0) snprintf(mdev->device_name, sizeof(mdev->device_name), "%s", DEVICE_BASE_NAME); else snprintf(mdev->device_name, sizeof(mdev->device_name), "%s%d", DEVICE_BASE_NAME, mdev->mdm_data.device_id); mdev->misc_device.minor = MISC_DYNAMIC_MINOR; mdev->misc_device.name = mdev->device_name; mdev->misc_device.fops = &mdm_modem_fops; memset((void *)&mdev->mdm_panic_blk, 0, sizeof(struct notifier_block)); mdev->mdm_panic_blk.notifier_call = mdm_panic_prep; atomic_notifier_chain_register(&panic_notifier_list, &mdev->mdm_panic_blk); /* MDM2AP_ERRFATAL */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "MDM2AP_ERRFATAL"); mdm_drv->mdm2ap_errfatal_gpio = pres ? pres->start : -1; /* AP2MDM_ERRFATAL */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "AP2MDM_ERRFATAL"); mdm_drv->ap2mdm_errfatal_gpio = pres ? pres->start : -1; /* MDM2AP_STATUS */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "MDM2AP_STATUS"); mdm_drv->mdm2ap_status_gpio = pres ? pres->start : -1; /* AP2MDM_STATUS */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "AP2MDM_STATUS"); mdm_drv->ap2mdm_status_gpio = pres ? pres->start : -1; /* MDM2AP_WAKEUP */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "MDM2AP_WAKEUP"); mdm_drv->mdm2ap_wakeup_gpio = pres ? pres->start : -1; /* AP2MDM_WAKEUP */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "AP2MDM_WAKEUP"); mdm_drv->ap2mdm_wakeup_gpio = pres ? pres->start : -1; /* AP2MDM_SOFT_RESET */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "AP2MDM_SOFT_RESET"); mdm_drv->ap2mdm_soft_reset_gpio = pres ? pres->start : -1; /* AP2MDM_KPDPWR_N */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "AP2MDM_KPDPWR_N"); mdm_drv->ap2mdm_kpdpwr_n_gpio = pres ? pres->start : -1; /* AP2MDM_PMIC_PWR_EN */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "AP2MDM_PMIC_PWR_EN"); mdm_drv->ap2mdm_pmic_pwr_en_gpio = pres ? pres->start : -1; /* MDM2AP_PBLRDY */ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "MDM2AP_PBLRDY"); mdm_drv->mdm2ap_pblrdy = pres ? pres->start : -1; /*USB_SW*/ pres = platform_get_resource_byname(pdev, IORESOURCE_IO, "USB_SW"); mdm_drv->usb_switch_gpio = pres ? pres->start : -1; mdm_drv->boot_type = CHARM_NORMAL_BOOT; mdm_drv->dump_timeout_ms = mdm_drv->pdata->ramdump_timeout_ms > 0 ? mdm_drv->pdata->ramdump_timeout_ms : MDM_RDUMP_TIMEOUT; init_completion(&mdev->mdm_needs_reload); init_completion(&mdev->mdm_boot); init_completion(&mdev->mdm_ram_dumps); mdev->first_boot = 1; mutex_init(&mdm_drv->peripheral_status_lock); } static void mdm_deconfigure_ipc(struct mdm_device *mdev) { struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; gpio_free(mdm_drv->ap2mdm_status_gpio); gpio_free(mdm_drv->ap2mdm_errfatal_gpio); if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio)) gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio); if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio)) gpio_free(mdm_drv->ap2mdm_pmic_pwr_en_gpio); gpio_free(mdm_drv->mdm2ap_status_gpio); gpio_free(mdm_drv->mdm2ap_errfatal_gpio); if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio)) gpio_free(mdm_drv->ap2mdm_soft_reset_gpio); if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio)) gpio_free(mdm_drv->ap2mdm_wakeup_gpio); if (mdev->mdm_queue) { destroy_workqueue(mdev->mdm_queue); mdev->mdm_queue = NULL; } if (mdev->mdm_sfr_queue) { destroy_workqueue(mdev->mdm_sfr_queue); mdev->mdm_sfr_queue = NULL; } } static int mdm_configure_ipc(struct mdm_device *mdev) { struct mdm_modem_drv *mdm_drv = &mdev->mdm_data; int ret = -1, irq; gpio_request(mdm_drv->ap2mdm_status_gpio, "AP2MDM_STATUS"); gpio_request(mdm_drv->ap2mdm_errfatal_gpio, "AP2MDM_ERRFATAL"); if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio)) gpio_request(mdm_drv->ap2mdm_kpdpwr_n_gpio, "AP2MDM_KPDPWR_N"); gpio_request(mdm_drv->mdm2ap_status_gpio, "MDM2AP_STATUS"); gpio_request(mdm_drv->mdm2ap_errfatal_gpio, "MDM2AP_ERRFATAL"); if (GPIO_IS_VALID(mdm_drv->mdm2ap_pblrdy)) gpio_request(mdm_drv->mdm2ap_pblrdy, "MDM2AP_PBLRDY"); if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio)) gpio_request(mdm_drv->ap2mdm_pmic_pwr_en_gpio, "AP2MDM_PMIC_PWR_EN"); if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio)) gpio_request(mdm_drv->ap2mdm_soft_reset_gpio, "AP2MDM_SOFT_RESET"); if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio)) gpio_request(mdm_drv->ap2mdm_wakeup_gpio, "AP2MDM_WAKEUP"); if (GPIO_IS_VALID(mdm_drv->usb_switch_gpio)) { if (gpio_request(mdm_drv->usb_switch_gpio, "USB_SW")) { pr_err("%s Failed to get usb switch gpio\n", __func__); mdm_drv->usb_switch_gpio = -1; } } gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 0); gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0); if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio)) 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); mdev->mdm_queue = alloc_workqueue("mdm_queue", 0, 0); if (!mdev->mdm_queue) { pr_err("%s: could not create mdm_queue for mdm id %d\n", __func__, mdev->mdm_data.device_id); ret = -ENOMEM; goto fatal_err; } mdev->mdm_sfr_queue = alloc_workqueue("mdm_sfr_queue", 0, 0); if (!mdev->mdm_sfr_queue) { pr_err("%s: could not create mdm_sfr_queue for mdm id %d\n", __func__, mdev->mdm_data.device_id); ret = -ENOMEM; goto fatal_err; } /* Register subsystem handlers */ mdev->mdm_subsys_dev = subsys_register(&mdev->mdm_subsys); if (IS_ERR(mdev->mdm_subsys_dev)) { ret = PTR_ERR(mdev->mdm_subsys_dev); goto fatal_err; } subsys_default_online(mdev->mdm_subsys_dev); /* ERR_FATAL irq. */ irq = gpio_to_irq(mdm_drv->mdm2ap_errfatal_gpio); if (irq < 0) { pr_err("%s: bad MDM2AP_ERRFATAL IRQ resource, err = %d\n", __func__, irq); goto errfatal_err; } ret = request_irq(irq, mdm_errfatal, IRQF_TRIGGER_RISING , "mdm errfatal", mdev); if (ret < 0) { pr_err("%s: MDM2AP_ERRFATAL IRQ#%d request failed, err=%d\n", __func__, irq, ret); goto errfatal_err; } mdev->mdm_errfatal_irq = irq; errfatal_err: /* status irq */ irq = gpio_to_irq(mdm_drv->mdm2ap_status_gpio); if (irq < 0) { pr_err("%s: bad MDM2AP_STATUS IRQ resource, err = %d\n", __func__, irq); goto status_err; } ret = request_threaded_irq(irq, NULL, mdm_status_change, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED, "mdm status", mdev); if (ret < 0) { pr_err("%s: MDM2AP_STATUS IRQ#%d request failed, err=%d", __func__, irq, ret); goto status_err; } mdev->mdm_status_irq = irq; status_err: if (GPIO_IS_VALID(mdm_drv->mdm2ap_pblrdy)) { irq = gpio_to_irq(mdm_drv->mdm2ap_pblrdy); if (irq < 0) { pr_err("%s: could not get MDM2AP_PBLRDY IRQ resource\n", __func__); goto pblrdy_err; } ret = request_threaded_irq(irq, NULL, mdm_pblrdy_change, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED, "mdm pbl ready", mdev); if (ret < 0) { pr_err("%s: MDM2AP_PBL IRQ#%d request failed error=%d\n", __func__, irq, ret); goto pblrdy_err; } mdev->mdm_pblrdy_irq = irq; } pblrdy_err: /* * If AP2MDM_PMIC_PWR_EN gpio is used, pull it high. It remains * high until the whole phone is shut down. */ if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio)) gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 1); return 0; fatal_err: mdm_deconfigure_ipc(mdev); return ret; } static int __devinit mdm_modem_probe(struct platform_device *pdev) { struct mdm_device *mdev = NULL; int ret = -1; mdev = kzalloc(sizeof(struct mdm_device), GFP_KERNEL); if (!mdev) { pr_err("%s: kzalloc fail.\n", __func__); ret = -ENOMEM; goto init_err; } platform_set_drvdata(pdev, mdev); mdm_modem_initialize_data(pdev, mdev); if (mdm_ops->debug_state_changed_cb) mdm_ops->debug_state_changed_cb(mdm_debug_mask); if (mdm_configure_ipc(mdev)) { pr_err("%s: mdm_configure_ipc failed, id = %d\n", __func__, mdev->mdm_data.device_id); goto init_err; } pr_debug("%s: Registering mdm id %d\n", __func__, mdev->mdm_data.device_id); ret = misc_register(&mdev->misc_device); if (ret) { pr_err("%s: failed registering mdm id %d, ret = %d\n", __func__, mdev->mdm_data.device_id, ret); mdm_deconfigure_ipc(mdev); goto init_err; } else { pr_err("%s: registered mdm id %d\n", __func__, mdev->mdm_data.device_id); mdm_device_list_add(mdev); INIT_DELAYED_WORK(&mdev->mdm2ap_status_check_work, mdm2ap_status_check); INIT_WORK(&mdev->mdm_status_work, mdm_status_fn); INIT_WORK(&mdev->sfr_reason_work, mdm_restart_reason_fn); /* Perform early powerup of the external modem in order to * allow tabla devices to be found. */ if (mdev->mdm_data.pdata->early_power_on) mdm_ops->power_on_mdm_cb(&mdev->mdm_data); } return ret; init_err: kfree(mdev); return ret; } static int __devexit mdm_modem_remove(struct platform_device *pdev) { int ret; struct mdm_device *mdev = platform_get_drvdata(pdev); pr_debug("%s: removing device id %d\n", __func__, mdev->mdm_data.device_id); mdm_deconfigure_ipc(mdev); ret = misc_deregister(&mdev->misc_device); mdm_device_list_remove(mdev); kfree(mdev); return ret; } static void mdm_modem_shutdown(struct platform_device *pdev) { struct mdm_modem_drv *mdm_drv; struct mdm_device *mdev = platform_get_drvdata(pdev); pr_debug("%s: shutting down device id %d\n", __func__, mdev->mdm_data.device_id); mdm_disable_irqs(mdev); mdm_drv = &mdev->mdm_data; mdm_ops->power_down_mdm_cb(mdm_drv); if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio)) gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 0); } static struct of_device_id mdm_match_table[] = { {.compatible = "qcom,mdm2_modem,mdm2_modem.1"}, {}, }; static struct platform_driver mdm_modem_driver = { .probe = mdm_modem_probe, .remove = __devexit_p(mdm_modem_remove), .shutdown = mdm_modem_shutdown, .driver = { .name = "mdm2_modem", .owner = THIS_MODULE, .of_match_table = mdm_match_table, }, }; static int __init mdm_modem_init(void) { int ret; ret = mdm_get_ops(&mdm_ops); if (ret) return ret; INIT_LIST_HEAD(&mdm_devices); mdm_debugfs_init(); return platform_driver_register(&mdm_modem_driver); } static void __exit mdm_modem_exit(void) { platform_driver_unregister(&mdm_modem_driver); } module_init(mdm_modem_init); module_exit(mdm_modem_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("mdm modem driver"); MODULE_VERSION("2.0"); MODULE_ALIAS("mdm_modem");