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: Sagar Dharia <sdharia@xxxxxxxxxxxxxx> --- drivers/slimbus/slimbus.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/slimbus.h | 65 +++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c index d74dfec..3cfa414 100644 --- a/drivers/slimbus/slimbus.c +++ b/drivers/slimbus/slimbus.c @@ -436,7 +436,9 @@ static int slim_register_controller(struct slim_controller *ctrl) ctrl->min_cg = SLIM_MIN_CLK_GEAR; if (!ctrl->max_cg) ctrl->max_cg = SLIM_MAX_CLK_GEAR; + init_completion(&ctrl->sched.pause_comp); mutex_init(&ctrl->m_ctrl); + mutex_init(&ctrl->sched.m_reconf); ret = device_register(&ctrl->dev); if (ret) goto out_list; @@ -1059,6 +1061,113 @@ int slim_request_clear_inf_element(struct slim_device *sb, } EXPORT_SYMBOL(slim_request_clear_inf_element); +/** + * slim_ctrl_clk_pause: Called by slimbus controller to enter/exit 'clock pause' + * 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. + * @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. + * 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, 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 < ctrl->last_tid; i++) { + /* Pending response for a message */ + if (ctrl->txnt[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_processtxn(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_processtxn(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_processtxn(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(slim_ctrl_clk_pause); + MODULE_LICENSE("GPL v2"); MODULE_VERSION("0.1"); MODULE_DESCRIPTION("Slimbus module"); diff --git a/include/linux/slimbus.h b/include/linux/slimbus.h index 924cdd3..f1099c5 100644 --- a/include/linux/slimbus.h +++ b/include/linux/slimbus.h @@ -114,11 +114,21 @@ struct slim_addrt { #define SLIM_MSG_MC_REPLY_VALUE 0x64 #define SLIM_MSG_MC_CHANGE_VALUE 0x68 +/* 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 + /* Destination type Values */ #define SLIM_MSG_DEST_LOGICALADDR 0 #define SLIM_MSG_DEST_ENUMADDR 1 #define SLIM_MSG_DEST_BROADCAST 3 +/* Clock pause values per slimbus spec */ +#define SLIM_CLK_FAST 0 +#define SLIM_CLK_CONST_PHASE 1 +#define SLIM_CLK_UNSPECIFIED 2 + /** * struct slim_val_inf: Slimbus value or information element * @start_offset: Specifies starting offset in information/value element map @@ -163,6 +173,39 @@ struct slim_msg_txn { }; /** + * 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 { + int clkgear; + enum slim_clk_state clk_state; + struct completion pause_comp; + struct mutex m_reconf; +}; + +/** * struct slim_controller: Controls every instance of SLIMbus * (similar to 'master' on SPI) * 'Manager device' is responsible for device management, bandwidth @@ -200,6 +243,7 @@ struct slim_msg_txn { * @txnt: Table of transactions having transaction ID * @txn_lock: Lock to protect table of transactions * @last_tid: size of the table txnt (can't grow beyond 256 since TID is 8-bits) + * @sched: scheduler structure used by the controller * @dev_released: completion used to signal when sysfs has released this * controller so that it can be deleted during shutdown * @xfer_msg: Transfer a message on this controller (this can be a broadcast @@ -211,6 +255,9 @@ struct slim_msg_txn { * @get_laddr: It is possible that controller needs to set fixed logical * address table and get_laddr can be used in that case so that controller * can do this assignment. + * @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. */ struct slim_controller { struct device dev; @@ -229,6 +276,7 @@ struct slim_controller { struct slim_val_inf *txnt[SLIM_MAX_TXNS]; u8 last_tid; spinlock_t txn_lock; + struct slim_sched sched; struct completion dev_released; int (*xfer_msg)(struct slim_controller *ctrl, struct slim_msg_txn *txn); @@ -236,6 +284,7 @@ struct slim_controller { struct slim_eaddr *ea, u8 laddr); int (*get_laddr)(struct slim_controller *ctrl, struct slim_eaddr *ea, u8 *laddr); + int (*wakeup)(struct slim_controller *ctrl); }; #define to_slim_controller(d) container_of(d, struct slim_controller, dev) @@ -521,4 +570,20 @@ static inline bool slim_tid_txn(u8 mt, u8 mc) } /* end of message apis */ +/** + * slim_ctrl_clk_pause: Called by slimbus controller to enter/exit 'clock pause' + * 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. + * @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. + * 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); + #endif /* _LINUX_SLIMBUS_H */ -- 1.8.2.1 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html