Merge tag 'rpmsg-v5.10' of git://git.kernel.org/pub/scm/linux/kernel/git/andersson...
[linux-2.6-microblaze.git] / drivers / soundwire / cadence_master.c
index 24eafe0..9fa5516 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
 #include <linux/soundwire/sdw_registers.h>
 #include <linux/soundwire/sdw.h>
 #include <sound/pcm_params.h>
@@ -50,11 +51,14 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask");
 #define CDNS_MCP_CONTROL_BLOCK_WAKEUP          BIT(0)
 
 #define CDNS_MCP_CMDCTRL                       0x8
+
+#define CDNS_MCP_CMDCTRL_INSERT_PARITY_ERR     BIT(2)
+
 #define CDNS_MCP_SSPSTAT                       0xC
 #define CDNS_MCP_FRAME_SHAPE                   0x10
 #define CDNS_MCP_FRAME_SHAPE_INIT              0x14
 #define CDNS_MCP_FRAME_SHAPE_COL_MASK          GENMASK(2, 0)
-#define CDNS_MCP_FRAME_SHAPE_ROW_OFFSET                3
+#define CDNS_MCP_FRAME_SHAPE_ROW_MASK          GENMASK(7, 3)
 
 #define CDNS_MCP_CONFIG_UPDATE                 0x18
 #define CDNS_MCP_CONFIG_UPDATE_BIT             BIT(0)
@@ -129,8 +133,7 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask");
 #define CDNS_MCP_CMD_SSP_TAG                   BIT(31)
 #define CDNS_MCP_CMD_COMMAND                   GENMASK(30, 28)
 #define CDNS_MCP_CMD_DEV_ADDR                  GENMASK(27, 24)
-#define CDNS_MCP_CMD_REG_ADDR_H                        GENMASK(23, 16)
-#define CDNS_MCP_CMD_REG_ADDR_L                        GENMASK(15, 8)
+#define CDNS_MCP_CMD_REG_ADDR                  GENMASK(23, 8)
 #define CDNS_MCP_CMD_REG_DATA                  GENMASK(7, 0)
 
 #define CDNS_MCP_CMD_READ                      2
@@ -172,6 +175,7 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask");
 #define CDNS_DPN_HCTRL_LCTRL                   GENMASK(10, 8)
 
 #define CDNS_PORTCTRL                          0x130
+#define CDNS_PORTCTRL_TEST_FAILED              BIT(1)
 #define CDNS_PORTCTRL_DIRN                     BIT(7)
 #define CDNS_PORTCTRL_BANK_INVERT              BIT(8)
 
@@ -367,6 +371,85 @@ static int cdns_hw_reset(void *data, u64 value)
 
 DEFINE_DEBUGFS_ATTRIBUTE(cdns_hw_reset_fops, NULL, cdns_hw_reset, "%llu\n");
 
+static int cdns_parity_error_injection(void *data, u64 value)
+{
+       struct sdw_cdns *cdns = data;
+       struct sdw_bus *bus;
+       int ret;
+
+       if (value != 1)
+               return -EINVAL;
+
+       bus = &cdns->bus;
+
+       /*
+        * Resume Master device. If this results in a bus reset, the
+        * Slave devices will re-attach and be re-enumerated.
+        */
+       ret = pm_runtime_get_sync(bus->dev);
+       if (ret < 0 && ret != -EACCES) {
+               dev_err_ratelimited(cdns->dev,
+                                   "pm_runtime_get_sync failed in %s, ret %d\n",
+                                   __func__, ret);
+               pm_runtime_put_noidle(bus->dev);
+               return ret;
+       }
+
+       /*
+        * wait long enough for Slave(s) to be in steady state. This
+        * does not need to be super precise.
+        */
+       msleep(200);
+
+       /*
+        * Take the bus lock here to make sure that any bus transactions
+        * will be queued while we inject a parity error on a dummy read
+        */
+       mutex_lock(&bus->bus_lock);
+
+       /* program hardware to inject parity error */
+       cdns_updatel(cdns, CDNS_MCP_CMDCTRL,
+                    CDNS_MCP_CMDCTRL_INSERT_PARITY_ERR,
+                    CDNS_MCP_CMDCTRL_INSERT_PARITY_ERR);
+
+       /* commit changes */
+       cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE,
+                    CDNS_MCP_CONFIG_UPDATE_BIT,
+                    CDNS_MCP_CONFIG_UPDATE_BIT);
+
+       /* do a broadcast dummy read to avoid bus clashes */
+       ret = sdw_bread_no_pm_unlocked(&cdns->bus, 0xf, SDW_SCP_DEVID_0);
+       dev_info(cdns->dev, "parity error injection, read: %d\n", ret);
+
+       /* program hardware to disable parity error */
+       cdns_updatel(cdns, CDNS_MCP_CMDCTRL,
+                    CDNS_MCP_CMDCTRL_INSERT_PARITY_ERR,
+                    0);
+
+       /* commit changes */
+       cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE,
+                    CDNS_MCP_CONFIG_UPDATE_BIT,
+                    CDNS_MCP_CONFIG_UPDATE_BIT);
+
+       /* Continue bus operation with parity error injection disabled */
+       mutex_unlock(&bus->bus_lock);
+
+       /* Userspace changed the hardware state behind the kernel's back */
+       add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+       /*
+        * allow Master device to enter pm_runtime suspend. This may
+        * also result in Slave devices suspending.
+        */
+       pm_runtime_mark_last_busy(bus->dev);
+       pm_runtime_put_autosuspend(bus->dev);
+
+       return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(cdns_parity_error_fops, NULL,
+                        cdns_parity_error_injection, "%llu\n");
+
 /**
  * sdw_cdns_debugfs_init() - Cadence debugfs init
  * @cdns: Cadence instance
@@ -378,6 +461,9 @@ void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root)
 
        debugfs_create_file("cdns-hw-reset", 0200, root, cdns,
                            &cdns_hw_reset_fops);
+
+       debugfs_create_file("cdns-parity-error-injection", 0200, root, cdns,
+                           &cdns_parity_error_fops);
 }
 EXPORT_SYMBOL_GPL(sdw_cdns_debugfs_init);
 
@@ -417,8 +503,7 @@ cdns_fill_msg_resp(struct sdw_cdns *cdns,
 
        /* fill response */
        for (i = 0; i < count; i++)
-               msg->buf[i + offset] = cdns->response_buf[i] >>
-                               SDW_REG_SHIFT(CDNS_MCP_RESP_RDATA);
+               msg->buf[i + offset] = FIELD_GET(CDNS_MCP_RESP_RDATA, cdns->response_buf[i]);
 
        return SDW_CMD_OK;
 }
@@ -441,14 +526,15 @@ _cdns_xfer_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int cmd,
        addr = msg->addr;
 
        for (i = 0; i < count; i++) {
-               data = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR);
-               data |= cmd << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND);
-               data |= addr++  << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
+               data = FIELD_PREP(CDNS_MCP_CMD_DEV_ADDR, msg->dev_num);
+               data |= FIELD_PREP(CDNS_MCP_CMD_COMMAND, cmd);
+               data |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, addr);
+               addr++;
 
                if (msg->flags == SDW_MSG_FLAG_WRITE)
                        data |= msg->buf[i + offset];
 
-               data |= msg->ssp_sync << SDW_REG_SHIFT(CDNS_MCP_CMD_SSP_TAG);
+               data |= FIELD_PREP(CDNS_MCP_CMD_SSP_TAG, msg->ssp_sync);
                cdns_writel(cdns, base, data);
                base += CDNS_MCP_CMD_WORD_LEN;
        }
@@ -483,12 +569,12 @@ cdns_program_scp_addr(struct sdw_cdns *cdns, struct sdw_msg *msg)
                cdns->msg_count = CDNS_SCP_RX_FIFOLEVEL;
        }
 
-       data[0] = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR);
-       data[0] |= 0x3 << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND);
+       data[0] = FIELD_PREP(CDNS_MCP_CMD_DEV_ADDR, msg->dev_num);
+       data[0] |= FIELD_PREP(CDNS_MCP_CMD_COMMAND, 0x3);
        data[1] = data[0];
 
-       data[0] |= SDW_SCP_ADDRPAGE1 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
-       data[1] |= SDW_SCP_ADDRPAGE2 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
+       data[0] |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, SDW_SCP_ADDRPAGE1);
+       data[1] |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, SDW_SCP_ADDRPAGE2);
 
        data[0] |= msg->addr_page1;
        data[1] |= msg->addr_page2;
@@ -785,13 +871,35 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id)
                dev_err_ratelimited(cdns->dev, "Bus clash for data word\n");
        }
 
+       if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL &&
+           int_status & CDNS_MCP_INT_DPINT) {
+               u32 port_intstat;
+
+               /* just log which ports report an error */
+               port_intstat = cdns_readl(cdns, CDNS_MCP_PORT_INTSTAT);
+               dev_err_ratelimited(cdns->dev, "DP interrupt: PortIntStat %8x\n",
+                                   port_intstat);
+
+               /* clear status w/ write1 */
+               cdns_writel(cdns, CDNS_MCP_PORT_INTSTAT, port_intstat);
+       }
+
        if (int_status & CDNS_MCP_INT_SLAVE_MASK) {
                /* Mask the Slave interrupt and wake thread */
                cdns_updatel(cdns, CDNS_MCP_INTMASK,
                             CDNS_MCP_INT_SLAVE_MASK, 0);
 
                int_status &= ~CDNS_MCP_INT_SLAVE_MASK;
-               schedule_work(&cdns->work);
+
+               /*
+                * Deal with possible race condition between interrupt
+                * handling and disabling interrupts on suspend.
+                *
+                * If the master is in the process of disabling
+                * interrupts, don't schedule a workqueue
+                */
+               if (cdns->interrupt_enabled)
+                       schedule_work(&cdns->work);
        }
 
        cdns_writel(cdns, CDNS_MCP_INTSTAT, int_status);
@@ -900,7 +1008,9 @@ int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state)
        mask |= CDNS_MCP_INT_CTRL_CLASH | CDNS_MCP_INT_DATA_CLASH |
                CDNS_MCP_INT_PARITY;
 
-       /* no detection of port interrupts for now */
+       /* port interrupt limited to test modes for now */
+       if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL)
+               mask |= CDNS_MCP_INT_DPINT;
 
        /* enable detection of RX fifo level */
        mask |= CDNS_MCP_INT_RX_WL;
@@ -924,6 +1034,19 @@ update_masks:
                slave_state = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT1);
                cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT1, slave_state);
        }
+       cdns->interrupt_enabled = state;
+
+       /*
+        * Complete any on-going status updates before updating masks,
+        * and cancel queued status updates.
+        *
+        * There could be a race with a new interrupt thrown before
+        * the 3 mask updates below are complete, so in the interrupt
+        * we use the 'interrupt_enabled' status to prevent new work
+        * from being queued.
+        */
+       if (!state)
+               cancel_work_sync(&cdns->work);
 
        cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK0, slave_intmask0);
        cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK1, slave_intmask1);
@@ -1041,9 +1164,10 @@ static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols)
        int r;
 
        r = sdw_find_row_index(n_rows);
-       c = sdw_find_col_index(n_cols) & CDNS_MCP_FRAME_SHAPE_COL_MASK;
+       c = sdw_find_col_index(n_cols);
 
-       val = (r << CDNS_MCP_FRAME_SHAPE_ROW_OFFSET) | c;
+       val = FIELD_PREP(CDNS_MCP_FRAME_SHAPE_ROW_MASK, r);
+       val |= FIELD_PREP(CDNS_MCP_FRAME_SHAPE_COL_MASK, c);
 
        return val;
 }
@@ -1170,12 +1294,9 @@ static int cdns_port_params(struct sdw_bus *bus,
 
        dpn_config = cdns_readl(cdns, dpn_config_off);
 
-       dpn_config |= ((p_params->bps - 1) <<
-                               SDW_REG_SHIFT(CDNS_DPN_CONFIG_WL));
-       dpn_config |= (p_params->flow_mode <<
-                               SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_FLOW));
-       dpn_config |= (p_params->data_mode <<
-                               SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_DAT));
+       u32p_replace_bits(&dpn_config, (p_params->bps - 1), CDNS_DPN_CONFIG_WL);
+       u32p_replace_bits(&dpn_config, p_params->flow_mode, CDNS_DPN_CONFIG_PORT_FLOW);
+       u32p_replace_bits(&dpn_config, p_params->data_mode, CDNS_DPN_CONFIG_PORT_DAT);
 
        cdns_writel(cdns, dpn_config_off, dpn_config);
 
@@ -1211,24 +1332,17 @@ static int cdns_transport_params(struct sdw_bus *bus,
        }
 
        dpn_config = cdns_readl(cdns, dpn_config_off);
-
-       dpn_config |= (t_params->blk_grp_ctrl <<
-                               SDW_REG_SHIFT(CDNS_DPN_CONFIG_BGC));
-       dpn_config |= (t_params->blk_pkg_mode <<
-                               SDW_REG_SHIFT(CDNS_DPN_CONFIG_BPM));
+       u32p_replace_bits(&dpn_config, t_params->blk_grp_ctrl, CDNS_DPN_CONFIG_BGC);
+       u32p_replace_bits(&dpn_config, t_params->blk_pkg_mode, CDNS_DPN_CONFIG_BPM);
        cdns_writel(cdns, dpn_config_off, dpn_config);
 
-       dpn_offsetctrl |= (t_params->offset1 <<
-                               SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_1));
-       dpn_offsetctrl |= (t_params->offset2 <<
-                               SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_2));
+       u32p_replace_bits(&dpn_offsetctrl, t_params->offset1, CDNS_DPN_OFFSET_CTRL_1);
+       u32p_replace_bits(&dpn_offsetctrl, t_params->offset2, CDNS_DPN_OFFSET_CTRL_2);
        cdns_writel(cdns, dpn_offsetctrl_off,  dpn_offsetctrl);
 
-       dpn_hctrl |= (t_params->hstart <<
-                               SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTART));
-       dpn_hctrl |= (t_params->hstop << SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTOP));
-       dpn_hctrl |= (t_params->lane_ctrl <<
-                               SDW_REG_SHIFT(CDNS_DPN_HCTRL_LCTRL));
+       u32p_replace_bits(&dpn_hctrl, t_params->hstart, CDNS_DPN_HCTRL_HSTART);
+       u32p_replace_bits(&dpn_hctrl, t_params->hstop, CDNS_DPN_HCTRL_HSTOP);
+       u32p_replace_bits(&dpn_hctrl, t_params->lane_ctrl, CDNS_DPN_HCTRL_LCTRL);
 
        cdns_writel(cdns, dpn_hctrl_off, dpn_hctrl);
        cdns_writel(cdns, dpn_samplectrl_off, (t_params->sample_interval - 1));
@@ -1526,15 +1640,20 @@ void sdw_cdns_config_stream(struct sdw_cdns *cdns,
 {
        u32 offset, val = 0;
 
-       if (dir == SDW_DATA_DIR_RX)
+       if (dir == SDW_DATA_DIR_RX) {
                val = CDNS_PORTCTRL_DIRN;
 
+               if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL)
+                       val |= CDNS_PORTCTRL_TEST_FAILED;
+       }
        offset = CDNS_PORTCTRL + pdi->num * CDNS_PORT_OFFSET;
-       cdns_updatel(cdns, offset, CDNS_PORTCTRL_DIRN, val);
+       cdns_updatel(cdns, offset,
+                    CDNS_PORTCTRL_DIRN | CDNS_PORTCTRL_TEST_FAILED,
+                    val);
 
        val = pdi->num;
        val |= CDNS_PDI_CONFIG_SOFT_RESET;
-       val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL);
+       val |= FIELD_PREP(CDNS_PDI_CONFIG_CHANNEL, (1 << ch) - 1);
        cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val);
 }
 EXPORT_SYMBOL(sdw_cdns_config_stream);