From 4b14e62ad3c9e6e6774517f4077e197c0537fb07 Mon Sep 17 00:00:00 2001 From: Sagar Dharia Date: Mon, 11 Dec 2017 23:43:01 +0000 Subject: slimbus: Add support for 'clock-pause' feature Per SLIMbus specification, a reconfiguration sequence known as 'clock pause' needs to be broadcast over the bus while entering low- power mode. Clock-pause is initiated by the controller driver. To exit clock-pause, controller typically wakes up the framer device. Since wakeup precedure is controller-specific, framework calls it via controller's function pointer to invoke it. Signed-off-by: Sagar Dharia Signed-off-by: Srinivas Kandagatla Reviwed-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/sched.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 drivers/slimbus/sched.c (limited to 'drivers/slimbus/sched.c') diff --git a/drivers/slimbus/sched.c b/drivers/slimbus/sched.c new file mode 100644 index 000000000000..af84997d2742 --- /dev/null +++ b/drivers/slimbus/sched.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + */ + +#include +#include "slimbus.h" + +/** + * slim_ctrl_clk_pause() - Called by slimbus controller to enter/exit + * 'clock pause' + * @ctrl: controller requesting bus to be paused or woken up + * @wakeup: Wakeup this controller from clock pause. + * @restart: Restart time value per spec used for clock pause. This value + * isn't used when controller is to be woken up. + * + * Slimbus specification needs this sequence to turn-off clocks for the bus. + * The sequence involves sending 3 broadcast messages (reconfiguration + * sequence) to inform all devices on the bus. + * To exit clock-pause, controller typically wakes up active framer device. + * This API executes clock pause reconfiguration sequence if wakeup is false. + * If wakeup is true, controller's wakeup is called. + * For entering clock-pause, -EBUSY is returned if a message txn in pending. + */ +int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, u8 restart) +{ + int i, ret = 0; + unsigned long flags; + struct slim_sched *sched = &ctrl->sched; + struct slim_val_inf msg = {0, 0, NULL, NULL}; + + DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION, + 3, SLIM_LA_MANAGER, &msg); + + if (wakeup == false && restart > SLIM_CLK_UNSPECIFIED) + return -EINVAL; + + mutex_lock(&sched->m_reconf); + if (wakeup) { + if (sched->clk_state == SLIM_CLK_ACTIVE) { + mutex_unlock(&sched->m_reconf); + return 0; + } + + /* + * Fine-tune calculation based on clock gear, + * message-bandwidth after bandwidth management + */ + ret = wait_for_completion_timeout(&sched->pause_comp, + msecs_to_jiffies(100)); + if (!ret) { + mutex_unlock(&sched->m_reconf); + pr_err("Previous clock pause did not finish"); + return -ETIMEDOUT; + } + ret = 0; + + /* + * Slimbus framework will call controller wakeup + * Controller should make sure that it sets active framer + * out of clock pause + */ + if (sched->clk_state == SLIM_CLK_PAUSED && ctrl->wakeup) + ret = ctrl->wakeup(ctrl); + if (!ret) + sched->clk_state = SLIM_CLK_ACTIVE; + mutex_unlock(&sched->m_reconf); + + return ret; + } + + /* already paused */ + if (ctrl->sched.clk_state == SLIM_CLK_PAUSED) { + mutex_unlock(&sched->m_reconf); + return 0; + } + + spin_lock_irqsave(&ctrl->txn_lock, flags); + for (i = 0; i < SLIM_MAX_TIDS; i++) { + /* Pending response for a message */ + if (idr_find(&ctrl->tid_idr, i)) { + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + mutex_unlock(&sched->m_reconf); + return -EBUSY; + } + } + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + + sched->clk_state = SLIM_CLK_ENTERING_PAUSE; + + /* clock pause sequence */ + ret = slim_do_transfer(ctrl, &txn); + if (ret) + goto clk_pause_ret; + + txn.mc = SLIM_MSG_MC_NEXT_PAUSE_CLOCK; + txn.rl = 4; + msg.num_bytes = 1; + msg.wbuf = &restart; + ret = slim_do_transfer(ctrl, &txn); + if (ret) + goto clk_pause_ret; + + txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW; + txn.rl = 3; + msg.num_bytes = 1; + msg.wbuf = NULL; + ret = slim_do_transfer(ctrl, &txn); + +clk_pause_ret: + if (ret) { + sched->clk_state = SLIM_CLK_ACTIVE; + } else { + sched->clk_state = SLIM_CLK_PAUSED; + complete(&sched->pause_comp); + } + mutex_unlock(&sched->m_reconf); + + return ret; +} +EXPORT_SYMBOL_GPL(slim_ctrl_clk_pause); -- cgit v1.2.3