diff options
-rw-r--r-- | drivers/soundwire/cadence_master.c | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index 8c354f946879..f1efe5beead6 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -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,6 +51,9 @@ 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 @@ -366,6 +370,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 @@ -377,6 +460,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); |