Commit 4b14e62a authored by Sagar Dharia's avatar Sagar Dharia Committed by Greg Kroah-Hartman

slimbus: Add support for 'clock-pause' feature

Per SLIMbus specification, a reconfiguration sequence known as
'clock pause' needs to be broadcast over the bus while entering low-
power mode. Clock-pause is initiated by the controller driver.
To exit clock-pause, controller typically wakes up the framer device.
Since wakeup precedure is controller-specific, framework calls it via
controller's function pointer to invoke it.
Signed-off-by: default avatarSagar Dharia <sdharia@codeaurora.org>
Signed-off-by: default avatarSrinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviwed-by: default avatarMark Brown <broonie@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent afbdcc7c
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
# Makefile for kernel SLIMbus framework. # Makefile for kernel SLIMbus framework.
# #
obj-$(CONFIG_SLIMBUS) += slimbus.o obj-$(CONFIG_SLIMBUS) += slimbus.o
slimbus-y := core.o messaging.o slimbus-y := core.o messaging.o sched.o
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/idr.h> #include <linux/idr.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/slimbus.h> #include <linux/slimbus.h>
#include "slimbus.h" #include "slimbus.h"
...@@ -218,6 +219,8 @@ int slim_register_controller(struct slim_controller *ctrl) ...@@ -218,6 +219,8 @@ int slim_register_controller(struct slim_controller *ctrl)
ida_init(&ctrl->laddr_ida); ida_init(&ctrl->laddr_ida);
idr_init(&ctrl->tid_idr); idr_init(&ctrl->tid_idr);
mutex_init(&ctrl->lock); mutex_init(&ctrl->lock);
mutex_init(&ctrl->sched.m_reconf);
init_completion(&ctrl->sched.pause_comp);
dev_dbg(ctrl->dev, "Bus [%s] registered:dev:%p\n", dev_dbg(ctrl->dev, "Bus [%s] registered:dev:%p\n",
ctrl->name, ctrl->dev); ctrl->name, ctrl->dev);
...@@ -249,6 +252,8 @@ int slim_unregister_controller(struct slim_controller *ctrl) ...@@ -249,6 +252,8 @@ int slim_unregister_controller(struct slim_controller *ctrl)
{ {
/* Remove all clients */ /* Remove all clients */
device_for_each_child(ctrl->dev, NULL, slim_ctrl_remove_device); device_for_each_child(ctrl->dev, NULL, slim_ctrl_remove_device);
/* Enter Clock Pause */
slim_ctrl_clk_pause(ctrl, false, 0);
ida_simple_remove(&ctrl_ida, ctrl->id); ida_simple_remove(&ctrl_ida, ctrl->id);
return 0; return 0;
...@@ -416,6 +421,14 @@ int slim_device_report_present(struct slim_controller *ctrl, ...@@ -416,6 +421,14 @@ int slim_device_report_present(struct slim_controller *ctrl,
struct slim_device *sbdev; struct slim_device *sbdev;
int ret; int ret;
ret = pm_runtime_get_sync(ctrl->dev);
if (ctrl->sched.clk_state != SLIM_CLK_ACTIVE) {
dev_err(ctrl->dev, "slim ctrl not active,state:%d, ret:%d\n",
ctrl->sched.clk_state, ret);
goto slimbus_not_active;
}
sbdev = slim_get_device(ctrl, e_addr); sbdev = slim_get_device(ctrl, e_addr);
if (IS_ERR(sbdev)) if (IS_ERR(sbdev))
return -ENODEV; return -ENODEV;
...@@ -427,6 +440,9 @@ int slim_device_report_present(struct slim_controller *ctrl, ...@@ -427,6 +440,9 @@ int slim_device_report_present(struct slim_controller *ctrl,
ret = slim_device_alloc_laddr(sbdev, true); ret = slim_device_alloc_laddr(sbdev, true);
slimbus_not_active:
pm_runtime_mark_last_busy(ctrl->dev);
pm_runtime_put_autosuspend(ctrl->dev);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(slim_device_report_present); EXPORT_SYMBOL_GPL(slim_device_report_present);
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
*/ */
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/pm_runtime.h>
#include "slimbus.h" #include "slimbus.h"
/** /**
...@@ -46,6 +47,10 @@ void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len) ...@@ -46,6 +47,10 @@ void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len)
memcpy(msg->rbuf, reply, len); memcpy(msg->rbuf, reply, len);
if (txn->comp) if (txn->comp)
complete(txn->comp); complete(txn->comp);
/* Remove runtime-pm vote now that response was received for TID txn */
pm_runtime_mark_last_busy(ctrl->dev);
pm_runtime_put_autosuspend(ctrl->dev);
} }
EXPORT_SYMBOL_GPL(slim_msg_response); EXPORT_SYMBOL_GPL(slim_msg_response);
...@@ -65,10 +70,29 @@ EXPORT_SYMBOL_GPL(slim_msg_response); ...@@ -65,10 +70,29 @@ EXPORT_SYMBOL_GPL(slim_msg_response);
int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn) int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn)
{ {
DECLARE_COMPLETION_ONSTACK(done); DECLARE_COMPLETION_ONSTACK(done);
bool need_tid; bool need_tid = false, clk_pause_msg = false;
unsigned long flags; unsigned long flags;
int ret, tid, timeout; int ret, tid, timeout;
/*
* do not vote for runtime-PM if the transactions are part of clock
* pause sequence
*/
if (ctrl->sched.clk_state == SLIM_CLK_ENTERING_PAUSE &&
(txn->mt == SLIM_MSG_MT_CORE &&
txn->mc >= SLIM_MSG_MC_BEGIN_RECONFIGURATION &&
txn->mc <= SLIM_MSG_MC_RECONFIGURE_NOW))
clk_pause_msg = true;
if (!clk_pause_msg) {
ret = pm_runtime_get_sync(ctrl->dev);
if (ctrl->sched.clk_state != SLIM_CLK_ACTIVE) {
dev_err(ctrl->dev, "ctrl wrong state:%d, ret:%d\n",
ctrl->sched.clk_state, ret);
goto slim_xfer_err;
}
}
need_tid = slim_tid_txn(txn->mt, txn->mc); need_tid = slim_tid_txn(txn->mt, txn->mc);
if (need_tid) { if (need_tid) {
...@@ -107,6 +131,15 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn) ...@@ -107,6 +131,15 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn)
dev_err(ctrl->dev, "Tx:MT:0x%x, MC:0x%x, LA:0x%x failed:%d\n", dev_err(ctrl->dev, "Tx:MT:0x%x, MC:0x%x, LA:0x%x failed:%d\n",
txn->mt, txn->mc, txn->la, ret); txn->mt, txn->mc, txn->la, ret);
slim_xfer_err:
if (!clk_pause_msg && (!need_tid || ret == -ETIMEDOUT)) {
/*
* remove runtime-pm vote if this was TX only, or
* if there was error during this transaction
*/
pm_runtime_mark_last_busy(ctrl->dev);
pm_runtime_mark_last_busy(ctrl->dev);
}
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(slim_do_transfer); EXPORT_SYMBOL_GPL(slim_do_transfer);
......
// 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);
...@@ -14,6 +14,16 @@ ...@@ -14,6 +14,16 @@
/* SLIMbus message types. Related to interpretation of message code. */ /* SLIMbus message types. Related to interpretation of message code. */
#define SLIM_MSG_MT_CORE 0x0 #define SLIM_MSG_MT_CORE 0x0
/* Clock pause Reconfiguration messages */
#define SLIM_MSG_MC_BEGIN_RECONFIGURATION 0x40
#define SLIM_MSG_MC_NEXT_PAUSE_CLOCK 0x4A
#define SLIM_MSG_MC_RECONFIGURE_NOW 0x5F
/* Clock pause values per SLIMbus spec */
#define SLIM_CLK_FAST 0
#define SLIM_CLK_CONST_PHASE 1
#define SLIM_CLK_UNSPECIFIED 2
/* Destination type Values */ /* Destination type Values */
#define SLIM_MSG_DEST_LOGICALADDR 0 #define SLIM_MSG_DEST_LOGICALADDR 0
#define SLIM_MSG_DEST_ENUMADDR 1 #define SLIM_MSG_DEST_ENUMADDR 1
...@@ -80,6 +90,42 @@ struct slim_msg_txn { ...@@ -80,6 +90,42 @@ struct slim_msg_txn {
#define DEFINE_SLIM_LDEST_TXN(name, mc, rl, la, msg) \ #define DEFINE_SLIM_LDEST_TXN(name, mc, rl, la, msg) \
struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_LOGICALADDR, 0,\ struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_LOGICALADDR, 0,\
0, la, msg, } 0, la, msg, }
#define DEFINE_SLIM_BCAST_TXN(name, mc, rl, la, msg) \
struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_BROADCAST, 0,\
0, la, msg, }
/**
* enum slim_clk_state: SLIMbus controller's clock state used internally for
* maintaining current clock state.
* @SLIM_CLK_ACTIVE: SLIMbus clock is active
* @SLIM_CLK_ENTERING_PAUSE: SLIMbus clock pause sequence is being sent on the
* bus. If this succeeds, state changes to SLIM_CLK_PAUSED. If the
* transition fails, state changes back to SLIM_CLK_ACTIVE
* @SLIM_CLK_PAUSED: SLIMbus controller clock has paused.
*/
enum slim_clk_state {
SLIM_CLK_ACTIVE,
SLIM_CLK_ENTERING_PAUSE,
SLIM_CLK_PAUSED,
};
/**
* struct slim_sched: Framework uses this structure internally for scheduling.
* @clk_state: Controller's clock state from enum slim_clk_state
* @pause_comp: Signals completion of clock pause sequence. This is useful when
* client tries to call SLIMbus transaction when controller is entering
* clock pause.
* @m_reconf: This mutex is held until current reconfiguration (data channel
* scheduling, message bandwidth reservation) is done. Message APIs can
* use the bus concurrently when this mutex is held since elemental access
* messages can be sent on the bus when reconfiguration is in progress.
*/
struct slim_sched {
enum slim_clk_state clk_state;
struct completion pause_comp;
struct mutex m_reconf;
};
/** /**
* struct slim_controller - Controls every instance of SLIMbus * struct slim_controller - Controls every instance of SLIMbus
* (similar to 'master' on SPI) * (similar to 'master' on SPI)
...@@ -95,6 +141,7 @@ struct slim_msg_txn { ...@@ -95,6 +141,7 @@ struct slim_msg_txn {
* @devices: Slim device list * @devices: Slim device list
* @tid_idr: tid id allocator * @tid_idr: tid id allocator
* @txn_lock: Lock to protect table of transactions * @txn_lock: Lock to protect table of transactions
* @sched: scheduler structure used by the controller
* @xfer_msg: Transfer a message on this controller (this can be a broadcast * @xfer_msg: Transfer a message on this controller (this can be a broadcast
* control/status message like data channel setup, or a unicast message * control/status message like data channel setup, or a unicast message
* like value element read/write. * like value element read/write.
...@@ -105,6 +152,9 @@ struct slim_msg_txn { ...@@ -105,6 +152,9 @@ struct slim_msg_txn {
* address table and get_laddr can be used in that case so that controller * address table and get_laddr can be used in that case so that controller
* can do this assignment. Use case is when the master is on the remote * can do this assignment. Use case is when the master is on the remote
* processor side, who is resposible for allocating laddr. * processor side, who is resposible for allocating laddr.
* @wakeup: This function pointer implements controller-specific procedure
* to wake it up from clock-pause. Framework will call this to bring
* the controller out of clock pause.
* *
* 'Manager device' is responsible for device management, bandwidth * 'Manager device' is responsible for device management, bandwidth
* allocation, channel setup, and port associations per channel. * allocation, channel setup, and port associations per channel.
...@@ -139,12 +189,14 @@ struct slim_controller { ...@@ -139,12 +189,14 @@ struct slim_controller {
struct list_head devices; struct list_head devices;
struct idr tid_idr; struct idr tid_idr;
spinlock_t txn_lock; spinlock_t txn_lock;
struct slim_sched sched;
int (*xfer_msg)(struct slim_controller *ctrl, int (*xfer_msg)(struct slim_controller *ctrl,
struct slim_msg_txn *tx); struct slim_msg_txn *tx);
int (*set_laddr)(struct slim_controller *ctrl, int (*set_laddr)(struct slim_controller *ctrl,
struct slim_eaddr *ea, u8 laddr); struct slim_eaddr *ea, u8 laddr);
int (*get_laddr)(struct slim_controller *ctrl, int (*get_laddr)(struct slim_controller *ctrl,
struct slim_eaddr *ea, u8 *laddr); struct slim_eaddr *ea, u8 *laddr);
int (*wakeup)(struct slim_controller *ctrl);
}; };
int slim_device_report_present(struct slim_controller *ctrl, int slim_device_report_present(struct slim_controller *ctrl,
...@@ -154,6 +206,7 @@ int slim_register_controller(struct slim_controller *ctrl); ...@@ -154,6 +206,7 @@ int slim_register_controller(struct slim_controller *ctrl);
int slim_unregister_controller(struct slim_controller *ctrl); int slim_unregister_controller(struct slim_controller *ctrl);
void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 l); void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 l);
int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn); int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn);
int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, u8 restart);
static inline bool slim_tid_txn(u8 mt, u8 mc) static inline bool slim_tid_txn(u8 mt, u8 mc)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment