mirror of https://gitee.com/openkylin/linux.git
122 lines
3.2 KiB
C
122 lines
3.2 KiB
C
|
// 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);
|