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