blob: d00790712d879fea4e15028ff602534ed8d3873e [file] [log] [blame]
/*******************************************************************
* (c) Copyright 2011-2012 Discretix Technologies Ltd. *
* This software is protected by copyright, international *
* treaties and patents, and distributed under multiple licenses. *
* Any use of this Software as part of the Discretix CryptoCell or *
* Packet Engine products requires a commercial license. *
* Copies of this Software that are distributed with the Discretix *
* CryptoCell or Packet Engine product drivers, may be used in *
* accordance with a commercial license, or at the user's option, *
* used and redistributed under the terms and conditions of the GNU *
* General Public License ("GPL") version 2, as published by the *
* Free Software Foundation. *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY LIABILITY AND WARRANTY; without even the implied *
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* See the GNU General Public License version 2 for more details. *
* You should have received a copy of the GNU General Public *
* License version 2 along with this Software; if not, please write *
* to the Free Software Foundation, Inc., 59 Temple Place - Suite *
* 330, Boston, MA 02111-1307, USA. *
* Any copy or reproduction of this Software, as permitted under *
* the GNU General Public License version 2, must include this *
* Copyright Notice as well as any other notices provided under *
* the said license. *
********************************************************************/
/**
* This file implements the power state control functions for SeP/CC
*/
#define SEP_LOG_CUR_COMPONENT SEP_LOG_MASK_SEP_POWER
#include <linux/workqueue.h>
#include <linux/pm_runtime.h>
/*#include <linux/export.h>*/
#define DX_CC_HOST_VIRT /* must be defined before including dx_cc_regs.h */
#include "dx_driver.h"
#include "dx_cc_regs.h"
#include "dx_init_cc_abi.h"
#include "sep_sw_desc.h"
#include "dx_sep_kapi.h"
#include <linux/delay.h>
#define SEP_STATE_CHANGE_TIMEOUT_MSEC 2500
/**
* struct sep_power_control - Control data for power state change operations
* @drvdata: The associated driver context
* @state_changed: Completion object to signal state change
* @last_state: Recorded last state
* @state_jiffies: jiffies at recorded last state
*
* last_state and state_jiffies are volatile because may be updated in
* the interrupt context while tested in _state_get function.
*/
struct sep_power_control {
struct sep_drvdata *drvdata;
struct completion state_changed;
volatile enum dx_sep_state last_state;
volatile unsigned long state_jiffies;
};
/* Global context for power management */
static struct sep_power_control power_control;
static const char *power_state_str(enum dx_sep_power_state pstate)
{
switch (pstate) {
case DX_SEP_POWER_INVALID:
return "INVALID";
case DX_SEP_POWER_OFF:
return "OFF";
case DX_SEP_POWER_BOOT:
return "BOOT";
case DX_SEP_POWER_IDLE:
return "IDLE";
case DX_SEP_POWER_ACTIVE:
return "ACTIVE";
case DX_SEP_POWER_HIBERNATED:
return "HIBERNATED";
}
return "(unknown)";
}
/**
* dx_sep_state_change_handler() - Interrupt handler for SeP state changes
* @drvdata: Associated driver context
*/
void dx_sep_state_change_handler(struct sep_drvdata *drvdata)
{
pr_warn("State=0x%08X Status/RetCode=0x%08X\n",
READ_REGISTER(drvdata->cc_base + SEP_STATE_GPR_OFFSET),
READ_REGISTER(drvdata->cc_base + SEP_STATUS_GPR_OFFSET));
power_control.state_jiffies = jiffies;
power_control.last_state = GET_SEP_STATE(drvdata);
complete(&power_control.state_changed);
}
/**
* dx_sep_wait_for_state() - Wait for SeP to reach one of the states reflected
* with given state mask
* @state_mask: The OR of expected SeP states
* @timeout_msec: Timeout of waiting for the state (in millisec.)
*
* Returns the state reached. In case of wait timeout the returned state
* may not be one of the expected states.
*/
enum dx_sep_state dx_sep_wait_for_state(u32 state_mask, int timeout_msec)
{
int wait_jiffies = msecs_to_jiffies(timeout_msec);
enum dx_sep_state sep_state;
do {
/* Poll for state transition completion or failure */
/* Arm for next state change before reading current state */
INIT_COMPLETION(power_control.state_changed);
sep_state = GET_SEP_STATE(power_control.drvdata);
if ((sep_state & state_mask) || (wait_jiffies == 0))
/* It's a match or wait timed out */
break;
wait_jiffies =
wait_for_completion_timeout(&power_control.state_changed,
wait_jiffies);
} while (1);
return sep_state;
}
void dx_sep_pm_runtime_get(void)
{
pm_runtime_get_sync(power_control.drvdata->dev);
}
void dx_sep_pm_runtime_put(void)
{
pm_runtime_mark_last_busy(power_control.drvdata->dev);
pm_runtime_put_autosuspend(power_control.drvdata->dev);
}
/**
* set_desc_qs_state() - Modify states of all Desc. queues
*
* @state: Requested new state
*/
static int set_desc_qs_state(enum desc_q_state state)
{
int i, rc;
for (i = 0, rc = 0;
(i < power_control.drvdata->num_of_desc_queues) && (rc == 0); i++)
rc = desc_q_set_state(power_control.drvdata->queue[i].
desc_queue, state);
if (unlikely(rc != 0))
/* Error - revert state of queues that were already changed */
for (i--; i >= 0; i--)
desc_q_set_state(power_control.drvdata->queue[i].
desc_queue,
(state ==
DESC_Q_ASLEEP) ? DESC_Q_ACTIVE :
DESC_Q_ASLEEP);
return rc;
}
static bool is_desc_qs_active(void)
{
int i;
enum desc_q_state qstate;
bool is_all_qs_active = true;
for (i = 0; i < power_control.drvdata->num_of_desc_queues; i++) {
qstate =
desc_q_get_state(power_control.drvdata->queue[i].
desc_queue);
if ((qstate != DESC_Q_ACTIVE)) {
is_all_qs_active = false;
break;
}
}
return is_all_qs_active;
}
static bool is_desc_qs_idle(unsigned long *idle_jiffies_p)
{
int i;
unsigned long this_q_idle_jiffies;
bool is_all_qs_idle = true;
*idle_jiffies_p = 0; /* Max. of both queues if both idle */
for (i = 0; i < power_control.drvdata->num_of_desc_queues; i++) {
if (!desc_q_is_idle(power_control.drvdata->queue[i].desc_queue,
&this_q_idle_jiffies)) {
is_all_qs_idle = false;
break;
}
if (this_q_idle_jiffies > *idle_jiffies_p)
*idle_jiffies_p = this_q_idle_jiffies;
}
return is_all_qs_idle;
}
/**
* reset_desc_qs() - Initiate clearing of desc. queue counters
* This function must be called only when transition to hibernation state
* is completed successfuly, i.e., the desc. queue is empty and asleep
*/
static void reset_desc_qs(void)
{
int i;
for (i = 0; i < power_control.drvdata->num_of_desc_queues; i++)
(void)desc_q_reset(power_control.drvdata->queue[i].desc_queue);
}
static int process_hibernation_req(void)
{
enum dx_sep_state sep_state;
int rc;
sep_state = GET_SEP_STATE(power_control.drvdata);
/* Already off, no need to send the sleep descriptor */
if (sep_state == DX_SEP_STATE_OFF ||
sep_state == DX_SEP_STATE_DONE_SLEEP_MODE)
return 0;
if (sep_state != DX_SEP_STATE_DONE_FW_INIT ||
!(is_desc_qs_active())) {
pr_err("Requested hibernation while SeP state=0x%08X\n",
sep_state);
return -EINVAL;
}
rc = set_desc_qs_state(DESC_Q_ASLEEP);
if (unlikely(rc != 0)) {
pr_err("Failed moving queues to SLEEP state (%d)\n", rc);
return rc;
}
/* Write value SEP_SLEEP_ENABLE command to GPR7 to initialize
sleep sequence */
WRITE_REGISTER(power_control.drvdata->cc_base +
DX_CC_REG_OFFSET(HOST, HOST_SEP_GPR7),
SEP_SLEEP_ENABLE);
/* Process state change */
sep_state = dx_sep_wait_for_state(DX_SEP_STATE_DONE_SLEEP_MODE,
SEP_STATE_CHANGE_TIMEOUT_MSEC);
switch (sep_state) {
case DX_SEP_STATE_DONE_SLEEP_MODE:
break;
case DX_SEP_STATE_DONE_FW_INIT:
pr_err("Transition to SLEEP mode aborted.\n");
rc = -EBUSY;
break;
case DX_SEP_STATE_PROC_SLEEP_MODE:
pr_err("Stuck in processing of SLEEP req.\n");
rc = -ETIME;
break;
default:
pr_err(
"Unexpected SeP state after SLEEP request: 0x%08X\n",
sep_state);
rc = -EINVAL;
}
if (unlikely(rc != 0)) {
sep_state = GET_SEP_STATE(power_control.drvdata);
if (sep_state == DX_SEP_STATE_DONE_FW_INIT)
/* Revert queues state on failure */
/* (if remained on active state) */
set_desc_qs_state(DESC_Q_ACTIVE);
} else {
reset_desc_qs();
}
return rc;
}
static int process_activate_req(void)
{
enum dx_sep_state sep_state;
int rc;
int count = 0;
sep_state = GET_SEP_STATE(power_control.drvdata);
if ((sep_state == DX_SEP_STATE_DONE_FW_INIT) && is_desc_qs_active()) {
pr_info("Requested activation when in active state\n");
return 0; /* Already in this state */
}
/* make sure Sep is not off before restore IMR */
if (sep_state == DX_SEP_STATE_OFF) {
while (count < SEP_POWERON_TIMEOUT) {
sep_state = GET_SEP_STATE(power_control.drvdata);
if (sep_state != DX_SEP_STATE_OFF)
break;
usleep_range(50, 150);
count++;
}
if (count >= SEP_TIMEOUT) {
pr_info("Timeout while waiting SEP poweron\n");
return -ETIME;
}
}
/* SeP may have been reset - restore IMR if SeP is not off */
/* This must be done before dx_sep_wait_for_state() */
WRITE_REGISTER(power_control.drvdata->cc_base +
DX_CC_REG_OFFSET(HOST, IMR),
~power_control.drvdata->irq_mask);
/* Nothing to initiate - just wait for FW_INIT_DONE */
sep_state = dx_sep_wait_for_state(DX_SEP_STATE_DONE_FW_INIT,
SEP_STATE_CHANGE_TIMEOUT_MSEC);
if (sep_state == DX_SEP_STATE_DONE_FW_INIT)
rc = set_desc_qs_state(DESC_Q_ACTIVE);
else {
pr_info("Timeout while waiting SEP wakeup\n");
rc = -ETIME; /* Timed out waiting */
}
return rc;
}
/**
* dx_sep_power_state_set() - Change power state of SeP (CC)
*
* @req_state: The requested power state (_HIBERNATED or _ACTIVE)
*
* Request changing of power state to given state and block until transition
* is completed.
* Requesting _HIBERNATED is allowed only from _ACTIVE state.
* Requesting _ACTIVE is allowed only after CC was powered back on (warm boot).
* Return codes:
* 0 - Power state change completed.
* -EINVAL - This request is not allowed in current SeP state or req_state
* value is invalid.
* -EBUSY - State change request ignored because SeP is busy (primarily,
* when requesting hibernation while SeP is processing something).
* -ETIME - Request timed out (primarily, when asking for _ACTIVE)
*/
int dx_sep_power_state_set(enum dx_sep_power_state req_state)
{
int rc = 0;
switch (req_state) {
case DX_SEP_POWER_HIBERNATED:
rc = process_hibernation_req();
break;
case DX_SEP_POWER_IDLE:
case DX_SEP_POWER_ACTIVE:
rc = process_activate_req();
break;
default:
pr_err("Invalid state to request (%s)\n",
power_state_str(req_state));
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(dx_sep_power_state_set);
/**
* dx_sep_power_state_get() - Get the current power state of SeP (CC)
* @state_jiffies_p: The "jiffies" value at which given state was detected.
*/
enum dx_sep_power_state dx_sep_power_state_get(unsigned long *state_jiffies_p)
{
enum dx_sep_state sep_state;
enum dx_sep_power_state rc;
unsigned long idle_jiffies;
sep_state = GET_SEP_STATE(power_control.drvdata);
if (sep_state != power_control.last_state) {
/* Probably off or after warm-boot with lost IMR */
/* Recover last_state */
power_control.last_state = sep_state;
power_control.state_jiffies = jiffies;
}
if (state_jiffies_p != NULL)
*state_jiffies_p = power_control.state_jiffies;
switch (sep_state) {
case DX_SEP_STATE_PROC_WARM_BOOT:
/*FALLTRHOUGH*/ case DX_SEP_STATE_DONE_WARM_BOOT:
rc = DX_SEP_POWER_BOOT;
break;
case DX_SEP_STATE_DONE_FW_INIT:
if (is_desc_qs_active()) {
if (is_desc_qs_idle(&idle_jiffies)) {
rc = DX_SEP_POWER_IDLE;
if (state_jiffies_p != NULL)
*state_jiffies_p = idle_jiffies;
} else {
rc = DX_SEP_POWER_ACTIVE;
}
} else {
/* SeP was woken up but dx_sep_power_state_set was not
* invoked to activate the queues */
rc = DX_SEP_POWER_BOOT;
}
break;
case DX_SEP_STATE_PROC_SLEEP_MODE:
/* Report as active until actually asleep */
rc = DX_SEP_POWER_ACTIVE;
break;
case DX_SEP_STATE_DONE_SLEEP_MODE:
rc = DX_SEP_POWER_HIBERNATED;
break;
case DX_SEP_STATE_OFF:
rc = DX_SEP_POWER_OFF;
break;
case DX_SEP_STATE_FATAL_ERROR:
default:/* Any state not supposed to happen for the driver */
rc = DX_SEP_POWER_INVALID;
}
return rc;
}
EXPORT_SYMBOL(dx_sep_power_state_get);
/**
* dx_sep_power_init() - Init resources for this module
*/
void dx_sep_power_init(struct sep_drvdata *drvdata)
{
power_control.drvdata = drvdata;
init_completion(&power_control.state_changed);
/* Init. recorded last state */
power_control.last_state = GET_SEP_STATE(drvdata);
power_control.state_jiffies = jiffies;
}
/**
* dx_sep_power_exit() - Cleanup resources for this module
*/
void dx_sep_power_exit(void)
{
}