aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/soundwire/cadence_master.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soundwire/cadence_master.c')
-rw-r--r--drivers/soundwire/cadence_master.c282
1 files changed, 240 insertions, 42 deletions
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c
index 9bec270d0fa4..ecd357d1c63d 100644
--- a/drivers/soundwire/cadence_master.c
+++ b/drivers/soundwire/cadence_master.c
@@ -183,7 +183,6 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask");
#define CDNS_PDI_CONFIG_PORT GENMASK(4, 0)
/* Driver defaults */
-#define CDNS_DEFAULT_SSP_INTERVAL 0x18
#define CDNS_TX_TIMEOUT 2000
#define CDNS_SCP_RX_FIFOLEVEL 0x2
@@ -211,34 +210,45 @@ static inline void cdns_updatel(struct sdw_cdns *cdns,
cdns_writel(cdns, offset, tmp);
}
-static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value)
+static int cdns_set_wait(struct sdw_cdns *cdns, int offset, u32 mask, u32 value)
{
int timeout = 10;
u32 reg_read;
- writel(value, cdns->registers + offset);
-
- /* Wait for bit to be self cleared */
+ /* Wait for bit to be set */
do {
reg_read = readl(cdns->registers + offset);
- if ((reg_read & value) == 0)
+ if ((reg_read & mask) == value)
return 0;
timeout--;
- udelay(50);
+ usleep_range(50, 100);
} while (timeout != 0);
- return -EAGAIN;
+ return -ETIMEDOUT;
+}
+
+static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value)
+{
+ writel(value, cdns->registers + offset);
+
+ /* Wait for bit to be self cleared */
+ return cdns_set_wait(cdns, offset, value, 0);
}
/*
* all changes to the MCP_CONFIG, MCP_CONTROL, MCP_CMDCTRL and MCP_PHYCTRL
* need to be confirmed with a write to MCP_CONFIG_UPDATE
*/
-static int cdns_update_config(struct sdw_cdns *cdns)
+static int cdns_config_update(struct sdw_cdns *cdns)
{
int ret;
+ if (sdw_cdns_is_clock_stop(cdns)) {
+ dev_err(cdns->dev, "Cannot program MCP_CONFIG_UPDATE in ClockStopMode\n");
+ return -EINVAL;
+ }
+
ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE,
CDNS_MCP_CONFIG_UPDATE_BIT);
if (ret < 0)
@@ -832,17 +842,36 @@ int sdw_cdns_exit_reset(struct sdw_cdns *cdns)
CDNS_MCP_CONTROL_HW_RST,
CDNS_MCP_CONTROL_HW_RST);
- /* enable bus operations with clock and data */
- cdns_updatel(cdns, CDNS_MCP_CONFIG,
- CDNS_MCP_CONFIG_OP,
- CDNS_MCP_CONFIG_OP_NORMAL);
-
/* commit changes */
- return cdns_update_config(cdns);
+ cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE,
+ CDNS_MCP_CONFIG_UPDATE_BIT,
+ CDNS_MCP_CONFIG_UPDATE_BIT);
+
+ /* don't wait here */
+ return 0;
+
}
EXPORT_SYMBOL(sdw_cdns_exit_reset);
/**
+ * sdw_cdns_enable_slave_interrupt() - Enable SDW slave interrupts
+ * @cdns: Cadence instance
+ * @state: boolean for true/false
+ */
+static void cdns_enable_slave_interrupts(struct sdw_cdns *cdns, bool state)
+{
+ u32 mask;
+
+ mask = cdns_readl(cdns, CDNS_MCP_INTMASK);
+ if (state)
+ mask |= CDNS_MCP_INT_SLAVE_MASK;
+ else
+ mask &= ~CDNS_MCP_INT_SLAVE_MASK;
+
+ cdns_writel(cdns, CDNS_MCP_INTMASK, mask);
+}
+
+/**
* sdw_cdns_enable_interrupt() - Enable SDW interrupts
* @cdns: Cadence instance
* @state: True if we are trying to enable interrupt.
@@ -1014,26 +1043,13 @@ static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols)
return val;
}
-/**
- * sdw_cdns_init() - Cadence initialization
- * @cdns: Cadence instance
- */
-int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit)
+static void cdns_init_clock_ctrl(struct sdw_cdns *cdns)
{
struct sdw_bus *bus = &cdns->bus;
struct sdw_master_prop *prop = &bus->prop;
u32 val;
+ u32 ssp_interval;
int divider;
- int ret;
-
- if (clock_stop_exit) {
- 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;
- }
- }
/* Set clock divider */
divider = (prop->mclk_freq / prop->max_clk_freq) - 1;
@@ -1052,8 +1068,23 @@ int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit)
cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, val);
/* Set SSP interval to default value */
- cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, CDNS_DEFAULT_SSP_INTERVAL);
- cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, CDNS_DEFAULT_SSP_INTERVAL);
+ ssp_interval = prop->default_frame_rate / SDW_CADENCE_GSYNC_HZ;
+ cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, ssp_interval);
+ cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, ssp_interval);
+}
+
+/**
+ * sdw_cdns_init() - Cadence initialization
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_init(struct sdw_cdns *cdns)
+{
+ u32 val;
+
+ cdns_init_clock_ctrl(cdns);
+
+ /* reset msg_count to default value of FIFOLEVEL */
+ cdns->msg_count = cdns_readl(cdns, CDNS_MCP_FIFOLEVEL);
/* flush command FIFOs */
cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_RST,
@@ -1066,25 +1097,31 @@ int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit)
/* Configure mcp config */
val = cdns_readl(cdns, CDNS_MCP_CONFIG);
- /* Set Max cmd retry to 15 */
- val |= CDNS_MCP_CONFIG_MCMD_RETRY;
+ /* enable bus operations with clock and data */
+ val &= ~CDNS_MCP_CONFIG_OP;
+ val |= CDNS_MCP_CONFIG_OP_NORMAL;
- /* Set frame delay between PREQ and ping frame to 15 frames */
- val |= 0xF << SDW_REG_SHIFT(CDNS_MCP_CONFIG_MPREQ_DELAY);
+ /* Set cmd mode for Tx and Rx cmds */
+ val &= ~CDNS_MCP_CONFIG_CMD;
+
+ /* Disable sniffer mode */
+ val &= ~CDNS_MCP_CONFIG_SNIFFER;
/* Disable auto bus release */
val &= ~CDNS_MCP_CONFIG_BUS_REL;
- /* Disable sniffer mode */
- val &= ~CDNS_MCP_CONFIG_SNIFFER;
+ if (cdns->bus.multi_link)
+ /* Set Multi-master mode to take gsync into account */
+ val |= CDNS_MCP_CONFIG_MMASTER;
- /* Set cmd mode for Tx and Rx cmds */
- val &= ~CDNS_MCP_CONFIG_CMD;
+ /* leave frame delay to hardware default of 0x1F */
+
+ /* leave command retry to hardware default of 0 */
cdns_writel(cdns, CDNS_MCP_CONFIG, val);
- /* commit changes */
- return cdns_update_config(cdns);
+ /* changes will be committed later */
+ return 0;
}
EXPORT_SYMBOL(sdw_cdns_init);
@@ -1218,6 +1255,166 @@ static const struct sdw_master_port_ops cdns_port_ops = {
};
/**
+ * sdw_cdns_is_clock_stop: Check clock status
+ *
+ * @cdns: Cadence instance
+ */
+bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns)
+{
+ return !!(cdns_readl(cdns, CDNS_MCP_STAT) & CDNS_MCP_STAT_CLK_STOP);
+}
+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;
+ }
+
+ /*
+ * Before entering clock stop we mask the Slave
+ * interrupts. This helps avoid having to deal with e.g. a
+ * Slave becoming UNATTACHED while the clock is being stopped
+ */
+ cdns_enable_slave_interrupts(cdns, false);
+
+ /*
+ * 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;
+
+ /* unmask Slave interrupts that were masked when stopping the clock */
+ cdns_enable_slave_interrupts(cdns, true);
+
+ 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
*/
@@ -1306,6 +1503,7 @@ void sdw_cdns_config_stream(struct sdw_cdns *cdns,
cdns_updatel(cdns, offset, CDNS_PORTCTRL_DIRN, val);
val = pdi->num;
+ val |= CDNS_PDI_CONFIG_SOFT_RESET;
val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL);
cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val);
}