diff options
Diffstat (limited to 'drivers/esoc/esoc-mdm-drv.c')
| -rw-r--r-- | drivers/esoc/esoc-mdm-drv.c | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/drivers/esoc/esoc-mdm-drv.c b/drivers/esoc/esoc-mdm-drv.c new file mode 100644 index 000000000000..9c2c68dfef65 --- /dev/null +++ b/drivers/esoc/esoc-mdm-drv.c @@ -0,0 +1,334 @@ +/* Copyright (c) 2013-2015, 2017, 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/delay.h> +#include <linux/workqueue.h> +#include <linux/reboot.h> +#include <linux/of.h> +#include "esoc.h" +#include "mdm-dbg.h" + +enum { + PWR_OFF = 0x1, + PWR_ON, + BOOT, + RUN, + CRASH, + IN_DEBUG, + SHUTDOWN, + RESET, + PEER_CRASH, +}; + +struct mdm_drv { + unsigned mode; + struct esoc_eng cmd_eng; + struct completion boot_done; + struct completion req_eng_wait; + struct esoc_clink *esoc_clink; + bool boot_fail; + struct workqueue_struct *mdm_queue; + struct work_struct ssr_work; + struct notifier_block esoc_restart; +}; +#define to_mdm_drv(d) container_of(d, struct mdm_drv, cmd_eng) + +static int esoc_msm_restart_handler(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct mdm_drv *mdm_drv = container_of(nb, struct mdm_drv, + esoc_restart); + struct esoc_clink *esoc_clink = mdm_drv->esoc_clink; + const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; + if (action == SYS_RESTART) { + if (mdm_dbg_stall_notify(ESOC_PRIMARY_REBOOT)) + return NOTIFY_OK; + dev_dbg(&esoc_clink->dev, "Notifying esoc of cold reboot\n"); + clink_ops->notify(ESOC_PRIMARY_REBOOT, esoc_clink); + } + return NOTIFY_OK; +} +static void mdm_handle_clink_evt(enum esoc_evt evt, + struct esoc_eng *eng) +{ + struct mdm_drv *mdm_drv = to_mdm_drv(eng); + switch (evt) { + case ESOC_INVALID_STATE: + mdm_drv->boot_fail = true; + complete(&mdm_drv->boot_done); + break; + case ESOC_RUN_STATE: + mdm_drv->boot_fail = false; + mdm_drv->mode = RUN, + complete(&mdm_drv->boot_done); + break; + case ESOC_UNEXPECTED_RESET: + case ESOC_ERR_FATAL: + /* + * Modem can crash while we are waiting for boot_done during + * a subsystem_get(). Setting mode to CRASH will prevent a + * subsequent subsystem_get() from entering poweron ops. Avoid + * this by seting mode to CRASH only if device was up and + * running. + */ + if (mdm_drv->mode == CRASH || mdm_drv->mode != RUN) + return; + mdm_drv->mode = CRASH; + queue_work(mdm_drv->mdm_queue, &mdm_drv->ssr_work); + break; + case ESOC_REQ_ENG_ON: + complete(&mdm_drv->req_eng_wait); + break; + default: + break; + } +} + +static void mdm_ssr_fn(struct work_struct *work) +{ + struct mdm_drv *mdm_drv = container_of(work, struct mdm_drv, ssr_work); + + /* + * If restarting esoc fails, the SSR framework triggers a kernel panic + */ + esoc_clink_request_ssr(mdm_drv->esoc_clink); + return; +} + +static void mdm_crash_shutdown(const struct subsys_desc *mdm_subsys) +{ + struct esoc_clink *esoc_clink = + container_of(mdm_subsys, + struct esoc_clink, + subsys); + const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; + if (mdm_dbg_stall_notify(ESOC_PRIMARY_CRASH)) + return; + clink_ops->notify(ESOC_PRIMARY_CRASH, esoc_clink); +} + +static int mdm_subsys_shutdown(const struct subsys_desc *crashed_subsys, + bool force_stop) +{ + int ret; + struct esoc_clink *esoc_clink = + container_of(crashed_subsys, struct esoc_clink, subsys); + struct mdm_drv *mdm_drv = esoc_get_drv_data(esoc_clink); + const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; + + if (mdm_drv->mode == CRASH || mdm_drv->mode == PEER_CRASH) { + if (mdm_dbg_stall_cmd(ESOC_PREPARE_DEBUG)) + /* We want to mask debug command. + * In this case return success + * to move to next stage + */ + return 0; + ret = clink_ops->cmd_exe(ESOC_PREPARE_DEBUG, + esoc_clink); + if (ret) { + dev_err(&esoc_clink->dev, "failed to enter debug\n"); + return ret; + } + mdm_drv->mode = IN_DEBUG; + } else if (!force_stop) { + if (esoc_clink->subsys.sysmon_shutdown_ret) + ret = clink_ops->cmd_exe(ESOC_FORCE_PWR_OFF, + esoc_clink); + else { + if (mdm_dbg_stall_cmd(ESOC_PWR_OFF)) + /* Since power off command is masked + * we return success, and leave the state + * of the command engine as is. + */ + return 0; + ret = clink_ops->cmd_exe(ESOC_PWR_OFF, esoc_clink); + } + if (ret) { + dev_err(&esoc_clink->dev, "failed to exe power off\n"); + return ret; + } + mdm_drv->mode = PWR_OFF; + } + return 0; +} + +static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys) +{ + int ret; + struct esoc_clink *esoc_clink = + container_of(crashed_subsys, struct esoc_clink, + subsys); + struct mdm_drv *mdm_drv = esoc_get_drv_data(esoc_clink); + const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; + int timeout = INT_MAX; + + if (!esoc_clink->auto_boot && !esoc_req_eng_enabled(esoc_clink)) { + dev_dbg(&esoc_clink->dev, "Wait for req eng registration\n"); + wait_for_completion(&mdm_drv->req_eng_wait); + } + if (mdm_drv->mode == PWR_OFF) { + if (mdm_dbg_stall_cmd(ESOC_PWR_ON)) + return -EBUSY; + ret = clink_ops->cmd_exe(ESOC_PWR_ON, esoc_clink); + if (ret) { + dev_err(&esoc_clink->dev, "pwr on fail\n"); + return ret; + } + } else if (mdm_drv->mode == IN_DEBUG) { + ret = clink_ops->cmd_exe(ESOC_EXIT_DEBUG, esoc_clink); + if (ret) { + dev_err(&esoc_clink->dev, "cannot exit debug mode\n"); + return ret; + } + mdm_drv->mode = PWR_OFF; + ret = clink_ops->cmd_exe(ESOC_PWR_ON, esoc_clink); + if (ret) { + dev_err(&esoc_clink->dev, "pwr on fail\n"); + return ret; + } + } + + /* + * In autoboot case, it is possible that we can forever wait for + * boot completion, when esoc fails to boot. This is because there + * is no helper application which can alert esoc driver about boot + * failure. Prevent going to wait forever in such case. + */ + if (esoc_clink->auto_boot) + timeout = 10 * HZ; + ret = wait_for_completion_timeout(&mdm_drv->boot_done, timeout); + if (mdm_drv->boot_fail || ret <= 0) { + dev_err(&esoc_clink->dev, "booting failed\n"); + return -EIO; + } + return 0; +} + +static int mdm_subsys_ramdumps(int want_dumps, + const struct subsys_desc *crashed_subsys) +{ + int ret; + struct esoc_clink *esoc_clink = + container_of(crashed_subsys, struct esoc_clink, + subsys); + const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; + + if (want_dumps) { + ret = clink_ops->cmd_exe(ESOC_EXE_DEBUG, esoc_clink); + if (ret) { + dev_err(&esoc_clink->dev, "debugging failed\n"); + return ret; + } + } + return 0; +} + +static int mdm_register_ssr(struct esoc_clink *esoc_clink) +{ + struct subsys_desc *subsys = &esoc_clink->subsys; + + subsys->shutdown = mdm_subsys_shutdown; + subsys->ramdump = mdm_subsys_ramdumps; + subsys->powerup = mdm_subsys_powerup; + subsys->crash_shutdown = mdm_crash_shutdown; + return esoc_clink_register_ssr(esoc_clink); +} + +int esoc_ssr_probe(struct esoc_clink *esoc_clink, struct esoc_drv *drv) +{ + int ret; + struct mdm_drv *mdm_drv; + struct esoc_eng *esoc_eng; + + mdm_drv = devm_kzalloc(&esoc_clink->dev, sizeof(*mdm_drv), GFP_KERNEL); + if (IS_ERR_OR_NULL(mdm_drv)) + return PTR_ERR(mdm_drv); + esoc_eng = &mdm_drv->cmd_eng; + esoc_eng->handle_clink_evt = mdm_handle_clink_evt; + ret = esoc_clink_register_cmd_eng(esoc_clink, esoc_eng); + if (ret) { + dev_err(&esoc_clink->dev, "failed to register cmd engine\n"); + return ret; + } + ret = mdm_register_ssr(esoc_clink); + if (ret) + goto ssr_err; + mdm_drv->mdm_queue = alloc_workqueue("mdm_drv_queue", 0, 0); + if (!mdm_drv->mdm_queue) { + dev_err(&esoc_clink->dev, "could not create mdm_queue\n"); + goto queue_err; + } + esoc_set_drv_data(esoc_clink, mdm_drv); + init_completion(&mdm_drv->boot_done); + init_completion(&mdm_drv->req_eng_wait); + INIT_WORK(&mdm_drv->ssr_work, mdm_ssr_fn); + mdm_drv->esoc_clink = esoc_clink; + mdm_drv->mode = PWR_OFF; + mdm_drv->boot_fail = false; + mdm_drv->esoc_restart.notifier_call = esoc_msm_restart_handler; + ret = register_reboot_notifier(&mdm_drv->esoc_restart); + if (ret) + dev_err(&esoc_clink->dev, "register for reboot failed\n"); + ret = mdm_dbg_eng_init(drv, esoc_clink); + if (ret) { + debug_init_done = false; + dev_err(&esoc_clink->dev, "dbg engine failure\n"); + } else { + dev_dbg(&esoc_clink->dev, "dbg engine initialized\n"); + debug_init_done = true; + } + return 0; +queue_err: + esoc_clink_unregister_ssr(esoc_clink); +ssr_err: + esoc_clink_unregister_cmd_eng(esoc_clink, esoc_eng); + return ret; +} + +static struct esoc_compat compat_table[] = { + { .name = "MDM9x25", + .data = NULL, + }, + { + .name = "MDM9x35", + .data = NULL, + }, + { + .name = "MDM9x55", + .data = NULL, + }, + { + .name = "MDM9x45", + .data = NULL, + }, + { + .name = "APQ8096", + .data = NULL, + }, +}; + +static struct esoc_drv esoc_ssr_drv = { + .owner = THIS_MODULE, + .probe = esoc_ssr_probe, + .compat_table = compat_table, + .compat_entries = ARRAY_SIZE(compat_table), + .driver = { + .name = "mdm-4x", + }, +}; + +int __init esoc_ssr_init(void) +{ + return esoc_drv_register(&esoc_ssr_drv); +} +module_init(esoc_ssr_init); +MODULE_LICENSE("GPL v2"); |
