From: Rander Wang rander.wang@intel.com
Add support for clock stop and restart, with two configuration parameters:
1) when entering the ClockStop mode, Slave-initiated wakes can be prevented.
2) When exiting the ClockStop mode, the caller can request a Bus Reset (either if all Slaves were configured in ClockStopMode1 or the Master IP lost context and enumeration is required)
The code handles the case where no Slaves are present by configuring the IP to treat COMMAND_IGNORED as success.
The exit_reset part can be dealt with in the caller, along with the required syncArm/syncGo sequence in multi-link mode.
Signed-off-by: Rander Wang rander.wang@intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/cadence_master.c | 157 +++++++++++++++++++++++++++++ drivers/soundwire/cadence_master.h | 2 + 2 files changed, 159 insertions(+)
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index d7f01d39767b..dc29556eaf94 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -231,6 +231,24 @@ static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value) return -EAGAIN; }
+static int cdns_set_wait(struct sdw_cdns *cdns, int offset, u32 mask, u32 value) +{ + int timeout = 10; + u32 reg_read; + + /* Wait for bit to be set */ + do { + reg_read = readl(cdns->registers + offset); + if ((reg_read & mask) == value) + return 0; + + timeout--; + usleep_range(50, 100); + } while (timeout != 0); + + return -ETIMEDOUT; +} + /* * all changes to the MCP_CONFIG, MCP_CONTROL, MCP_CMDCTRL and MCP_PHYCTRL * need to be confirmed with a write to MCP_CONFIG_UPDATE @@ -1223,6 +1241,145 @@ bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns) } EXPORT_SYMBOL(sdw_cdns_is_clock_stop);
+/** + * sdw_cdns_clock_stop: Cadence clock stop configuration routine + * + * @cdns: Cadence instance + * @block_wake: prevent wakes if required by the platform + */ +int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake) +{ + bool slave_present = false; + struct sdw_slave *slave; + int ret; + + /* Check suspend status */ + if (sdw_cdns_is_clock_stop(cdns)) { + dev_dbg(cdns->dev, "Clock is already stopped\n"); + return 0; + } + + /* + * For specific platforms, it is required to be able to put + * master into a state in which it ignores wake-up trials + * in clock stop state + */ + if (block_wake) + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_BLOCK_WAKEUP, + CDNS_MCP_CONTROL_BLOCK_WAKEUP); + + list_for_each_entry(slave, &cdns->bus.slaves, node) { + if (slave->status == SDW_SLAVE_ATTACHED || + slave->status == SDW_SLAVE_ALERT) { + slave_present = true; + break; + } + } + + /* + * This CMD_ACCEPT should be used when there are no devices + * attached on the link when entering clock stop mode. If this is + * not set and there is a broadcast write then the command ignored + * will be treated as a failure + */ + if (!slave_present) + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CMD_ACCEPT, + CDNS_MCP_CONTROL_CMD_ACCEPT); + else + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CMD_ACCEPT, 0); + + /* commit changes */ + ret = cdns_config_update(cdns); + if (ret < 0) { + dev_err(cdns->dev, "%s: config_update failed\n", __func__); + return ret; + } + + /* Prepare slaves for clock stop */ + ret = sdw_bus_prep_clk_stop(&cdns->bus); + if (ret < 0) { + dev_err(cdns->dev, "prepare clock stop failed %d", ret); + return ret; + } + + /* + * Enter clock stop mode and only report errors if there are + * Slave devices present (ALERT or ATTACHED) + */ + ret = sdw_bus_clk_stop(&cdns->bus); + if (ret < 0 && slave_present && ret != -ENODATA) { + dev_err(cdns->dev, "bus clock stop failed %d", ret); + return ret; + } + + ret = cdns_set_wait(cdns, CDNS_MCP_STAT, + CDNS_MCP_STAT_CLK_STOP, + CDNS_MCP_STAT_CLK_STOP); + if (ret < 0) + dev_err(cdns->dev, "Clock stop failed %d\n", ret); + + return ret; +} +EXPORT_SYMBOL(sdw_cdns_clock_stop); + +/** + * sdw_cdns_clock_restart: Cadence PM clock restart configuration routine + * + * @cdns: Cadence instance + * @bus_reset: context may be lost while in low power modes and the bus + * may require a Severe Reset and re-enumeration after a wake. + */ +int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset) +{ + int ret; + + ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CLK_STOP_CLR); + if (ret < 0) { + dev_err(cdns->dev, "Couldn't exit from clock stop\n"); + return ret; + } + + ret = cdns_set_wait(cdns, CDNS_MCP_STAT, CDNS_MCP_STAT_CLK_STOP, 0); + if (ret < 0) { + dev_err(cdns->dev, "clock stop exit failed %d\n", ret); + return ret; + } + + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_BLOCK_WAKEUP, 0); + + /* + * clear CMD_ACCEPT so that the command ignored + * will be treated as a failure during a broadcast write + */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT, 0); + + if (!bus_reset) { + + /* enable bus operations with clock and data */ + cdns_updatel(cdns, CDNS_MCP_CONFIG, + CDNS_MCP_CONFIG_OP, + CDNS_MCP_CONFIG_OP_NORMAL); + + ret = cdns_config_update(cdns); + if (ret < 0) { + dev_err(cdns->dev, "%s: config_update failed\n", __func__); + return ret; + } + + ret = sdw_bus_exit_clk_stop(&cdns->bus); + if (ret < 0) + dev_err(cdns->dev, "bus failed to exit clock stop %d\n", ret); + } + + return ret; +} +EXPORT_SYMBOL(sdw_cdns_clock_restart); + /** * sdw_cdns_probe() - Cadence probe routine * @cdns: Cadence instance diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 691faa386889..e8fa5c7e09f4 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -145,6 +145,8 @@ int sdw_cdns_exit_reset(struct sdw_cdns *cdns); int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state);
bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns); +int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake); +int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset);
#ifdef CONFIG_DEBUG_FS void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root);