| // 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); |