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