esoc: Add support for autoboot

Some of the external SoC are flash based and can boot independently.
Extend esoc driver to support such auto boot esocs.

This patch also adds support for primary esoc. Primary esoc are
esoc that control secondary esoc such as modems. Primary esoc have
control over reset/poweroff of secondary esoc. Secondary esoc don't
have control over reset/poweroff of primary esoc. In general modems
are considered as secondary esoc while apps processor is considered
as primary esoc.

Change-Id: Id02417fcd122ac108cf75d3381ee7955f0f8f783
Signed-off-by: Arun KS <arunks@codeaurora.org>
Signed-off-by: Srivatsa Vaddagiri <vatsa@codeaurora.org>
This commit is contained in:
Arun KS 2017-01-16 17:47:03 +05:30
parent 5c846e4f7c
commit fe1cc57aaf
4 changed files with 96 additions and 19 deletions

View file

@ -179,19 +179,37 @@ static int mdm_cmd_exe(enum esoc_cmd cmd, struct esoc_clink *esoc)
struct device *dev = mdm->dev; struct device *dev = mdm->dev;
int ret; int ret;
bool graceful_shutdown = false; bool graceful_shutdown = false;
u32 status, err_fatal;
switch (cmd) { switch (cmd) {
case ESOC_PWR_ON: case ESOC_PWR_ON:
if (esoc->auto_boot) {
/*
* If esoc has already booted, we would have missed
* status change interrupt. Read status and err_fatal
* signals to arrive at the state of esoc.
*/
esoc->clink_ops->get_status(&status, esoc);
esoc->clink_ops->get_err_fatal(&err_fatal, esoc);
if (err_fatal)
return -EIO;
if (status && !mdm->ready) {
mdm->ready = true;
esoc->clink_ops->notify(ESOC_BOOT_DONE, esoc);
}
}
gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 0); gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 0);
mdm_enable_irqs(mdm);
mdm->init = 1; mdm->init = 1;
mdm_do_first_power_on(mdm); mdm_do_first_power_on(mdm);
mdm_enable_irqs(mdm);
break; break;
case ESOC_PWR_OFF: case ESOC_PWR_OFF:
mdm_disable_irqs(mdm); mdm_disable_irqs(mdm);
mdm->debug = 0; mdm->debug = 0;
mdm->ready = false; mdm->ready = false;
mdm->trig_cnt = 0; mdm->trig_cnt = 0;
if (esoc->primary)
break;
graceful_shutdown = true; graceful_shutdown = true;
ret = sysmon_send_shutdown(&esoc->subsys); ret = sysmon_send_shutdown(&esoc->subsys);
if (ret) { if (ret) {
@ -228,6 +246,8 @@ force_poff:
esoc->subsys.sysmon_shutdown_ret); esoc->subsys.sysmon_shutdown_ret);
} }
if (esoc->primary)
break;
/* /*
* Force a shutdown of the mdm. This is required in order * Force a shutdown of the mdm. This is required in order
* to prevent the mdm from immediately powering back on * to prevent the mdm from immediately powering back on
@ -249,9 +269,12 @@ force_poff:
*/ */
mdm->ready = false; mdm->ready = false;
cancel_delayed_work(&mdm->mdm2ap_status_check_work); cancel_delayed_work(&mdm->mdm2ap_status_check_work);
if (!mdm->esoc->auto_boot) {
gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 1); gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 1);
dev_dbg(mdm->dev, "set ap2mdm errfatal to force reset\n"); dev_dbg(mdm->dev,
"set ap2mdm errfatal to force reset\n");
msleep(mdm->ramdump_delay_ms); msleep(mdm->ramdump_delay_ms);
}
break; break;
case ESOC_EXE_DEBUG: case ESOC_EXE_DEBUG:
mdm->debug = 1; mdm->debug = 1;
@ -378,6 +401,8 @@ static void mdm_notify(enum esoc_notify notify, struct esoc_clink *esoc)
status_down = false; status_down = false;
dev_dbg(dev, "signal apq err fatal for graceful restart\n"); dev_dbg(dev, "signal apq err fatal for graceful restart\n");
gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 1); gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 1);
if (esoc->primary)
break;
timeout = local_clock(); timeout = local_clock();
do_div(timeout, NSEC_PER_MSEC); do_div(timeout, NSEC_PER_MSEC);
timeout += MDM_MODEM_TIMEOUT; timeout += MDM_MODEM_TIMEOUT;
@ -441,11 +466,25 @@ static irqreturn_t mdm_status_change(int irq, void *dev_id)
return IRQ_HANDLED; return IRQ_HANDLED;
dev = mdm->dev; dev = mdm->dev;
esoc = mdm->esoc; esoc = mdm->esoc;
/*
* On auto boot devices, there is a possibility of receiving
* status change interrupt before esoc_clink structure is
* initialized. Ignore them.
*/
if (!esoc)
return IRQ_HANDLED;
value = gpio_get_value(MDM_GPIO(mdm, MDM2AP_STATUS)); value = gpio_get_value(MDM_GPIO(mdm, MDM2AP_STATUS));
if (value == 0 && mdm->ready) { if (value == 0 && mdm->ready) {
dev_err(dev, "unexpected reset external modem\n"); dev_err(dev, "unexpected reset external modem\n");
esoc_clink_evt_notify(ESOC_UNEXPECTED_RESET, esoc); esoc_clink_evt_notify(ESOC_UNEXPECTED_RESET, esoc);
} else if (value == 1) { } else if (value == 1) {
/*
* In auto_boot cases, bailout early if mdm
* is up already.
*/
if (esoc->auto_boot && mdm->ready)
return IRQ_HANDLED;
cancel_delayed_work(&mdm->mdm2ap_status_check_work); cancel_delayed_work(&mdm->mdm2ap_status_check_work);
dev_dbg(dev, "status = 1: mdm is now ready\n"); dev_dbg(dev, "status = 1: mdm is now ready\n");
mdm->ready = true; mdm->ready = true;
@ -453,6 +492,8 @@ static irqreturn_t mdm_status_change(int irq, void *dev_id)
queue_work(mdm->mdm_queue, &mdm->mdm_status_work); queue_work(mdm->mdm_queue, &mdm->mdm_status_work);
if (mdm->get_restart_reason) if (mdm->get_restart_reason)
queue_work(mdm->mdm_queue, &mdm->restart_reason_work); queue_work(mdm->mdm_queue, &mdm->restart_reason_work);
if (esoc->auto_boot)
esoc->clink_ops->notify(ESOC_BOOT_DONE, esoc);
} }
return IRQ_HANDLED; return IRQ_HANDLED;
} }
@ -582,13 +623,21 @@ static int mdm_configure_ipc(struct mdm_ctrl *mdm, struct platform_device *pdev)
&mdm->ramdump_delay_ms); &mdm->ramdump_delay_ms);
if (ret) if (ret)
mdm->ramdump_delay_ms = DEF_RAMDUMP_DELAY; mdm->ramdump_delay_ms = DEF_RAMDUMP_DELAY;
/* Multilple gpio_request calls are allowed */ /*
* In certain scenarios, multiple esoc devices are monitoring
* same AP2MDM_STATUS line. But only one of them will have a
* successful gpio_request call. Initialize gpio only if request
* succeeds.
*/
if (gpio_request(MDM_GPIO(mdm, AP2MDM_STATUS), "AP2MDM_STATUS")) if (gpio_request(MDM_GPIO(mdm, AP2MDM_STATUS), "AP2MDM_STATUS"))
dev_err(dev, "Failed to configure AP2MDM_STATUS gpio\n"); dev_err(dev, "Failed to configure AP2MDM_STATUS gpio\n");
/* Multilple gpio_request calls are allowed */ else
gpio_direction_output(MDM_GPIO(mdm, AP2MDM_STATUS), 0);
if (gpio_request(MDM_GPIO(mdm, AP2MDM_ERRFATAL), "AP2MDM_ERRFATAL")) if (gpio_request(MDM_GPIO(mdm, AP2MDM_ERRFATAL), "AP2MDM_ERRFATAL"))
dev_err(dev, "%s Failed to configure AP2MDM_ERRFATAL gpio\n", dev_err(dev, "%s Failed to configure AP2MDM_ERRFATAL gpio\n",
__func__); __func__);
else
gpio_direction_output(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 0);
if (gpio_request(MDM_GPIO(mdm, MDM2AP_STATUS), "MDM2AP_STATUS")) { if (gpio_request(MDM_GPIO(mdm, MDM2AP_STATUS), "MDM2AP_STATUS")) {
dev_err(dev, "%s Failed to configure MDM2AP_STATUS gpio\n", dev_err(dev, "%s Failed to configure MDM2AP_STATUS gpio\n",
__func__); __func__);
@ -621,9 +670,6 @@ static int mdm_configure_ipc(struct mdm_ctrl *mdm, struct platform_device *pdev)
} }
} }
gpio_direction_output(MDM_GPIO(mdm, AP2MDM_STATUS), 0);
gpio_direction_output(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 0);
if (gpio_is_valid(MDM_GPIO(mdm, AP2MDM_CHNLRDY))) if (gpio_is_valid(MDM_GPIO(mdm, AP2MDM_CHNLRDY)))
gpio_direction_output(MDM_GPIO(mdm, AP2MDM_CHNLRDY), 0); gpio_direction_output(MDM_GPIO(mdm, AP2MDM_CHNLRDY), 0);

View file

@ -13,6 +13,7 @@
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/reboot.h> #include <linux/reboot.h>
#include <linux/of.h>
#include "esoc.h" #include "esoc.h"
#include "mdm-dbg.h" #include "mdm-dbg.h"
@ -72,7 +73,14 @@ static void mdm_handle_clink_evt(enum esoc_evt evt,
break; break;
case ESOC_UNEXPECTED_RESET: case ESOC_UNEXPECTED_RESET:
case ESOC_ERR_FATAL: case ESOC_ERR_FATAL:
if (mdm_drv->mode == CRASH) /*
* 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; return;
mdm_drv->mode = CRASH; mdm_drv->mode = CRASH;
queue_work(mdm_drv->mdm_queue, &mdm_drv->ssr_work); queue_work(mdm_drv->mdm_queue, &mdm_drv->ssr_work);
@ -161,8 +169,9 @@ static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys)
subsys); subsys);
struct mdm_drv *mdm_drv = esoc_get_drv_data(esoc_clink); struct mdm_drv *mdm_drv = esoc_get_drv_data(esoc_clink);
const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops;
int timeout = INT_MAX;
if (!esoc_req_eng_enabled(esoc_clink)) { if (!esoc_clink->auto_boot && !esoc_req_eng_enabled(esoc_clink)) {
dev_dbg(&esoc_clink->dev, "Wait for req eng registration\n"); dev_dbg(&esoc_clink->dev, "Wait for req eng registration\n");
wait_for_completion(&mdm_drv->req_eng_wait); wait_for_completion(&mdm_drv->req_eng_wait);
} }
@ -187,8 +196,17 @@ static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys)
return ret; return ret;
} }
} }
wait_for_completion(&mdm_drv->boot_done);
if (mdm_drv->boot_fail) { /*
* 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"); dev_err(&esoc_clink->dev, "booting failed\n");
return -EIO; return -EIO;
} }
@ -216,10 +234,12 @@ static int mdm_subsys_ramdumps(int want_dumps,
static int mdm_register_ssr(struct esoc_clink *esoc_clink) static int mdm_register_ssr(struct esoc_clink *esoc_clink)
{ {
esoc_clink->subsys.shutdown = mdm_subsys_shutdown; struct subsys_desc *subsys = &esoc_clink->subsys;
esoc_clink->subsys.ramdump = mdm_subsys_ramdumps;
esoc_clink->subsys.powerup = mdm_subsys_powerup; subsys->shutdown = mdm_subsys_shutdown;
esoc_clink->subsys.crash_shutdown = mdm_crash_shutdown; subsys->ramdump = mdm_subsys_ramdumps;
subsys->powerup = mdm_subsys_powerup;
subsys->crash_shutdown = mdm_crash_shutdown;
return esoc_clink_register_ssr(esoc_clink); return esoc_clink_register_ssr(esoc_clink);
} }

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. /* Copyright (c) 2014-2015, 2017, The Linux Foundation. All rights reserved.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License version 2 and
@ -68,6 +68,9 @@ static int mdm4x_do_first_power_on(struct mdm_ctrl *mdm)
struct device *dev = mdm->dev; struct device *dev = mdm->dev;
dev_dbg(dev, "Powering on modem for the first time\n"); dev_dbg(dev, "Powering on modem for the first time\n");
if (mdm->esoc->auto_boot)
return 0;
mdm_toggle_soft_reset(mdm, false); mdm_toggle_soft_reset(mdm, false);
/* Add a delay to allow PON sequence to complete*/ /* Add a delay to allow PON sequence to complete*/
msleep(50); msleep(50);
@ -134,6 +137,9 @@ static int mdm9x55_power_down(struct mdm_ctrl *mdm)
static void mdm4x_cold_reset(struct mdm_ctrl *mdm) static void mdm4x_cold_reset(struct mdm_ctrl *mdm)
{ {
if (!gpio_is_valid(MDM_GPIO(mdm, AP2MDM_SOFT_RESET)))
return;
dev_dbg(mdm->dev, "Triggering mdm cold reset"); dev_dbg(mdm->dev, "Triggering mdm cold reset");
gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET), gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
!!mdm->soft_reset_inverted); !!mdm->soft_reset_inverted);

View file

@ -60,6 +60,9 @@ struct esoc_eng {
* @subsys_desc: descriptor for subsystem restart * @subsys_desc: descriptor for subsystem restart
* @subsys_dev: ssr device handle. * @subsys_dev: ssr device handle.
* @np: device tree node for esoc_clink. * @np: device tree node for esoc_clink.
* @auto_boot: boots independently.
* @primary: primary esoc controls(reset/poweroff) all secondary
* esocs, but not otherway around.
*/ */
struct esoc_clink { struct esoc_clink {
const char *name; const char *name;
@ -79,6 +82,8 @@ struct esoc_clink {
struct subsys_desc subsys; struct subsys_desc subsys;
struct subsys_device *subsys_dev; struct subsys_device *subsys_dev;
struct device_node *np; struct device_node *np;
bool auto_boot;
bool primary;
}; };
/** /**