| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2011-2017, The Linux Foundation | |
| */ | |
| #include <linux/errno.h> | |
| #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); |