summaryrefslogtreecommitdiffstats
path: root/drivers/soundwire
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-10-01 22:59:55 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-10-01 22:59:55 +0200
commit4cb1a880e7f774e59c2ebb68187d4811ad9d0c4e (patch)
treecaa439723e5fc5cdc473bdd519a0a5f4ac27d201 /drivers/soundwire
parentc471bf4b11c7df0f0f9f42b5aeec424dc62d0c7e (diff)
parent0173f525b2c1b02a51784e2119d434593235aed1 (diff)
downloadlinux-4cb1a880e7f774e59c2ebb68187d4811ad9d0c4e.tar.gz
linux-4cb1a880e7f774e59c2ebb68187d4811ad9d0c4e.tar.xz
Merge tag 'soundwire-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire into char-misc-next
Vinod writes: soundwire updates for 5.10-rc1 This round of update includes: - Generic bandwidth allocation algorithm from Intel folks - PM support for Intel chipsets - Updates to Intel drivers which makes sdw usable on latest laptops - Support for MMIO SDW controllers found in QC chipsets - Update to subsystem to use helpers in bitfield.h to manage register bits * tag 'soundwire-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire: (66 commits) soundwire: sysfs: add slave status and device number before probe soundwire: bus: add enumerated Slave device to device list soundwire: remove an unnecessary NULL check soundwire: cadence: add data port test fail interrupt soundwire: intel: enable test modes soundwire: enable Data Port test modes soundwire: intel: use {u32|u16}p_replace_bits soundwire: cadence: use u32p_replace_bits soundwire: qcom: get max rows and cols info from compatible soundwire: qcom: add support to block packing mode soundwire: qcom: clear BIT FIELDs before value set. soundwire: Add generic bandwidth allocation algorithm soundwire: cadence: add parity error injection through debugfs soundwire: bus: export broadcast read/write capability for tests ASoC: codecs: realtek-soundwire: ignore initial PARITY errors soundwire: bus: use quirk to filter out invalid parity errors soundwire: slave: add first_interrupt_done status soundwire: bus: filter-out unwanted interrupt reports ASoC/soundwire: bus: use property to set interrupt masks soundwire: qcom: fix SLIBMUS/SLIMBUS typo ...
Diffstat (limited to 'drivers/soundwire')
-rw-r--r--drivers/soundwire/Kconfig7
-rw-r--r--drivers/soundwire/Makefile3
-rw-r--r--drivers/soundwire/bus.c120
-rw-r--r--drivers/soundwire/bus.h52
-rw-r--r--drivers/soundwire/bus_type.c9
-rw-r--r--drivers/soundwire/cadence_master.c199
-rw-r--r--drivers/soundwire/cadence_master.h5
-rw-r--r--drivers/soundwire/generic_bandwidth_allocation.c425
-rw-r--r--drivers/soundwire/intel.c803
-rw-r--r--drivers/soundwire/intel.h4
-rw-r--r--drivers/soundwire/intel_init.c22
-rw-r--r--drivers/soundwire/master.c2
-rw-r--r--drivers/soundwire/mipi_disco.c18
-rw-r--r--drivers/soundwire/qcom.c118
-rw-r--r--drivers/soundwire/slave.c13
-rw-r--r--drivers/soundwire/stream.c45
-rw-r--r--drivers/soundwire/sysfs_local.h4
-rw-r--r--drivers/soundwire/sysfs_slave.c58
18 files changed, 1671 insertions, 236 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig
index fa2b4ab92ed9..016e74230bb7 100644
--- a/drivers/soundwire/Kconfig
+++ b/drivers/soundwire/Kconfig
@@ -24,6 +24,7 @@ config SOUNDWIRE_CADENCE
config SOUNDWIRE_INTEL
tristate "Intel SoundWire Master driver"
select SOUNDWIRE_CADENCE
+ select SOUNDWIRE_GENERIC_ALLOCATION
depends on ACPI && SND_SOC
help
SoundWire Intel Master driver.
@@ -33,11 +34,15 @@ config SOUNDWIRE_INTEL
config SOUNDWIRE_QCOM
tristate "Qualcomm SoundWire Master driver"
- depends on SLIMBUS
+ imply SLIMBUS
depends on SND_SOC
help
SoundWire Qualcomm Master driver.
If you have an Qualcomm platform which has a SoundWire Master then
enable this config option to get the SoundWire support for that
device
+
+config SOUNDWIRE_GENERIC_ALLOCATION
+ tristate
+
endif
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile
index 7c53ffae9f50..bf1e250d50dd 100644
--- a/drivers/soundwire/Makefile
+++ b/drivers/soundwire/Makefile
@@ -8,6 +8,9 @@ soundwire-bus-y := bus_type.o bus.o master.o slave.o mipi_disco.o stream.o \
sysfs_slave.o sysfs_slave_dpn.o
obj-$(CONFIG_SOUNDWIRE) += soundwire-bus.o
+soundwire-generic-allocation-objs := generic_bandwidth_allocation.o
+obj-$(CONFIG_SOUNDWIRE_GENERIC_ALLOCATION) += soundwire-generic-allocation.o
+
ifdef CONFIG_DEBUG_FS
soundwire-bus-y += debugfs.o
endif
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c
index da0201693c24..8eaf31e76677 100644
--- a/drivers/soundwire/bus.c
+++ b/drivers/soundwire/bus.c
@@ -61,6 +61,12 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent,
return -EINVAL;
}
+ if (!bus->compute_params) {
+ dev_err(bus->dev,
+ "Bandwidth allocation not configured, compute_params no set\n");
+ return -EINVAL;
+ }
+
mutex_init(&bus->msg_lock);
mutex_init(&bus->bus_lock);
INIT_LIST_HEAD(&bus->slaves);
@@ -255,6 +261,21 @@ static int sdw_reset_page(struct sdw_bus *bus, u16 dev_num)
return ret;
}
+static int sdw_transfer_unlocked(struct sdw_bus *bus, struct sdw_msg *msg)
+{
+ int ret;
+
+ ret = do_transfer(bus, msg);
+ if (ret != 0 && ret != -ENODATA)
+ dev_err(bus->dev, "trf on Slave %d failed:%d\n",
+ msg->dev_num, ret);
+
+ if (msg->page)
+ sdw_reset_page(bus, msg->dev_num);
+
+ return ret;
+}
+
/**
* sdw_transfer() - Synchronous transfer message to a SDW Slave device
* @bus: SDW bus
@@ -266,13 +287,7 @@ int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg)
mutex_lock(&bus->msg_lock);
- ret = do_transfer(bus, msg);
- if (ret != 0 && ret != -ENODATA)
- dev_err(bus->dev, "trf on Slave %d failed:%d\n",
- msg->dev_num, ret);
-
- if (msg->page)
- sdw_reset_page(bus, msg->dev_num);
+ ret = sdw_transfer_unlocked(bus, msg);
mutex_unlock(&bus->msg_lock);
@@ -347,8 +362,8 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
return -EINVAL;
}
- msg->addr_page1 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE1_MASK));
- msg->addr_page2 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE2_MASK));
+ msg->addr_page1 = FIELD_GET(SDW_SCP_ADDRPAGE1_MASK, addr);
+ msg->addr_page2 = FIELD_GET(SDW_SCP_ADDRPAGE2_MASK, addr);
msg->addr |= BIT(15);
msg->page = true;
@@ -428,6 +443,39 @@ sdw_bwrite_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value)
return sdw_transfer(bus, &msg);
}
+int sdw_bread_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr)
+{
+ struct sdw_msg msg;
+ u8 buf;
+ int ret;
+
+ ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num,
+ SDW_MSG_FLAG_READ, &buf);
+ if (ret)
+ return ret;
+
+ ret = sdw_transfer_unlocked(bus, &msg);
+ if (ret < 0)
+ return ret;
+
+ return buf;
+}
+EXPORT_SYMBOL(sdw_bread_no_pm_unlocked);
+
+int sdw_bwrite_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value)
+{
+ struct sdw_msg msg;
+ int ret;
+
+ ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num,
+ SDW_MSG_FLAG_WRITE, &value);
+ if (ret)
+ return ret;
+
+ return sdw_transfer_unlocked(bus, &msg);
+}
+EXPORT_SYMBOL(sdw_bwrite_no_pm_unlocked);
+
static int
sdw_read_no_pm(struct sdw_slave *slave, u32 addr)
{
@@ -699,6 +747,15 @@ static int sdw_program_device_num(struct sdw_bus *bus)
if (!found) {
/* TODO: Park this device in Group 13 */
+
+ /*
+ * add Slave device even if there is no platform
+ * firmware description. There will be no driver probe
+ * but the user/integration will be able to see the
+ * device, enumeration status and device number in sysfs
+ */
+ sdw_slave_add(bus, &id, NULL);
+
dev_err(bus->dev, "Slave Entry not found\n");
}
@@ -1051,6 +1108,12 @@ int sdw_configure_dpn_intr(struct sdw_slave *slave,
int ret;
u8 val = 0;
+ if (slave->bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL) {
+ dev_dbg(&slave->dev, "TEST FAIL interrupt %s\n",
+ enable ? "on" : "off");
+ mask |= SDW_DPN_INT_TEST_FAIL;
+ }
+
addr = SDW_DPN_INTMASK(port);
/* Set/Clear port ready interrupt mask */
@@ -1184,13 +1247,13 @@ static int sdw_initialize_slave(struct sdw_slave *slave)
return ret;
/*
- * Set bus clash, parity and SCP implementation
- * defined interrupt mask
- * TODO: Read implementation defined interrupt mask
- * from Slave property
+ * Set SCP_INT1_MASK register, typically bus clash and
+ * implementation-defined interrupt mask. The Parity detection
+ * may not always be correct on startup so its use is
+ * device-dependent, it might e.g. only be enabled in
+ * steady-state after a couple of frames.
*/
- val = SDW_SCP_INT1_IMPL_DEF | SDW_SCP_INT1_BUS_CLASH |
- SDW_SCP_INT1_PARITY;
+ val = slave->prop.scp_int1_mask;
/* Enable SCP interrupts */
ret = sdw_update(slave, SDW_SCP_INTMASK1, val, val);
@@ -1362,6 +1425,8 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
unsigned long port;
bool slave_notify = false;
u8 buf, buf2[2], _buf, _buf2[2];
+ bool parity_check;
+ bool parity_quirk;
sdw_modify_slave_status(slave, SDW_SLAVE_ALERT);
@@ -1394,12 +1459,18 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
* interrupt
*/
if (buf & SDW_SCP_INT1_PARITY) {
- dev_err(&slave->dev, "Parity error detected\n");
+ parity_check = slave->prop.scp_int1_mask & SDW_SCP_INT1_PARITY;
+ parity_quirk = !slave->first_interrupt_done &&
+ (slave->prop.quirks & SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY);
+
+ if (parity_check && !parity_quirk)
+ dev_err(&slave->dev, "Parity error detected\n");
clear |= SDW_SCP_INT1_PARITY;
}
if (buf & SDW_SCP_INT1_BUS_CLASH) {
- dev_err(&slave->dev, "Bus clash error detected\n");
+ if (slave->prop.scp_int1_mask & SDW_SCP_INT1_BUS_CLASH)
+ dev_err(&slave->dev, "Bus clash detected\n");
clear |= SDW_SCP_INT1_BUS_CLASH;
}
@@ -1411,16 +1482,18 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
*/
if (buf & SDW_SCP_INT1_IMPL_DEF) {
- dev_dbg(&slave->dev, "Slave impl defined interrupt\n");
+ if (slave->prop.scp_int1_mask & SDW_SCP_INT1_IMPL_DEF) {
+ dev_dbg(&slave->dev, "Slave impl defined interrupt\n");
+ slave_notify = true;
+ }
clear |= SDW_SCP_INT1_IMPL_DEF;
- slave_notify = true;
}
/* Check port 0 - 3 interrupts */
port = buf & SDW_SCP_INT1_PORT0_3;
/* To get port number corresponding to bits, shift it */
- port = port >> SDW_REG_SHIFT(SDW_SCP_INT1_PORT0_3);
+ port = FIELD_GET(SDW_SCP_INT1_PORT0_3, port);
for_each_set_bit(bit, &port, 8) {
sdw_handle_port_interrupt(slave, bit,
&port_status[bit]);
@@ -1468,6 +1541,9 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
goto io_err;
}
+ /* at this point all initial interrupt sources were handled */
+ slave->first_interrupt_done = true;
+
/*
* Read status again to ensure no new interrupts arrived
* while servicing interrupts.
@@ -1670,8 +1746,10 @@ void sdw_clear_slave_status(struct sdw_bus *bus, u32 request)
if (!slave)
continue;
- if (slave->status != SDW_SLAVE_UNATTACHED)
+ if (slave->status != SDW_SLAVE_UNATTACHED) {
sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
+ slave->first_interrupt_done = false;
+ }
/* keep track of request, used in pm_runtime resume */
slave->unattach_request = request;
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h
index 82484f741168..2e049d39c6e5 100644
--- a/drivers/soundwire/bus.h
+++ b/drivers/soundwire/bus.h
@@ -19,6 +19,8 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus)
int sdw_of_find_slaves(struct sdw_bus *bus);
void sdw_extract_slave_id(struct sdw_bus *bus,
u64 addr, struct sdw_slave_id *id);
+int sdw_slave_add(struct sdw_bus *bus, struct sdw_slave_id *id,
+ struct fwnode_handle *fwnode);
int sdw_master_device_add(struct sdw_bus *bus, struct device *parent,
struct fwnode_handle *fwnode);
int sdw_master_device_del(struct sdw_bus *bus);
@@ -69,6 +71,7 @@ struct sdw_msg {
};
#define SDW_DOUBLE_RATE_FACTOR 2
+#define SDW_STRM_RATE_GROUPING 1
extern int sdw_rows[SDW_FRAME_ROWS];
extern int sdw_cols[SDW_FRAME_COLS];
@@ -154,9 +157,50 @@ int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg,
int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf);
+/* Retrieve and return channel count from channel mask */
+static inline int sdw_ch_mask_to_ch(int ch_mask)
+{
+ int c = 0;
+
+ for (c = 0; ch_mask; ch_mask >>= 1)
+ c += ch_mask & 1;
+
+ return c;
+}
+
+/* Fill transport parameter data structure */
+static inline void sdw_fill_xport_params(struct sdw_transport_params *params,
+ int port_num, bool grp_ctrl_valid,
+ int grp_ctrl, int sample_int,
+ int off1, int off2,
+ int hstart, int hstop,
+ int pack_mode, int lane_ctrl)
+{
+ params->port_num = port_num;
+ params->blk_grp_ctrl_valid = grp_ctrl_valid;
+ params->blk_grp_ctrl = grp_ctrl;
+ params->sample_interval = sample_int;
+ params->offset1 = off1;
+ params->offset2 = off2;
+ params->hstart = hstart;
+ params->hstop = hstop;
+ params->blk_pkg_mode = pack_mode;
+ params->lane_ctrl = lane_ctrl;
+}
+
+/* Fill port parameter data structure */
+static inline void sdw_fill_port_params(struct sdw_port_params *params,
+ int port_num, int bps,
+ int flow_mode, int data_mode)
+{
+ params->num = port_num;
+ params->bps = bps;
+ params->flow_mode = flow_mode;
+ params->data_mode = data_mode;
+}
+
/* Read-Modify-Write Slave register */
-static inline int
-sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
+static inline int sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
{
int tmp;
@@ -168,6 +212,10 @@ sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
return sdw_write(slave, addr, tmp);
}
+/* broadcast read/write for tests */
+int sdw_bread_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr);
+int sdw_bwrite_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value);
+
/*
* At the moment we only track Master-initiated hw_reset.
* Additional fields can be added as needed
diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c
index 6fba55898cf0..575b9bad99d5 100644
--- a/drivers/soundwire/bus_type.c
+++ b/drivers/soundwire/bus_type.c
@@ -84,6 +84,15 @@ static int sdw_drv_probe(struct device *dev)
const struct sdw_device_id *id;
int ret;
+ /*
+ * fw description is mandatory to bind
+ */
+ if (!dev->fwnode)
+ return -ENODEV;
+
+ if (!IS_ENABLED(CONFIG_ACPI) && !dev->of_node)
+ return -ENODEV;
+
id = sdw_get_device_id(slave, drv);
if (!id)
return -ENODEV;
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c
index 24eafe0aa1c3..9fa55164354a 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,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);
diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h
index 7638858397df..4d1aab5b5ec2 100644
--- a/drivers/soundwire/cadence_master.h
+++ b/drivers/soundwire/cadence_master.h
@@ -84,6 +84,8 @@ struct sdw_cdns_stream_config {
* @bus: Bus handle
* @stream_type: Stream type
* @link_id: Master link id
+ * @hw_params: hw_params to be applied in .prepare step
+ * @suspended: status set when suspended, to be used in .prepare
*/
struct sdw_cdns_dma_data {
char *name;
@@ -92,6 +94,8 @@ struct sdw_cdns_dma_data {
struct sdw_bus *bus;
enum sdw_stream_type stream_type;
int link_id;
+ struct snd_pcm_hw_params *hw_params;
+ bool suspended;
};
/**
@@ -129,6 +133,7 @@ struct sdw_cdns {
bool link_up;
unsigned int msg_count;
+ bool interrupt_enabled;
struct work_struct work;
diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c
new file mode 100644
index 000000000000..0bdef38c9a30
--- /dev/null
+++ b/drivers/soundwire/generic_bandwidth_allocation.c
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+// Copyright(c) 2015-2020 Intel Corporation.
+
+/*
+ * Bandwidth management algorithm based on 2^n gears
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw.h>
+#include "bus.h"
+
+#define SDW_STRM_RATE_GROUPING 1
+
+struct sdw_group_params {
+ unsigned int rate;
+ int full_bw;
+ int payload_bw;
+ int hwidth;
+};
+
+struct sdw_group {
+ unsigned int count;
+ unsigned int max_size;
+ unsigned int *rates;
+};
+
+struct sdw_transport_data {
+ int hstart;
+ int hstop;
+ int block_offset;
+ int sub_block_offset;
+};
+
+static void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt,
+ struct sdw_transport_data *t_data)
+{
+ struct sdw_slave_runtime *s_rt = NULL;
+ struct sdw_port_runtime *p_rt;
+ int port_bo, sample_int;
+ unsigned int rate, bps, ch = 0;
+ unsigned int slave_total_ch;
+ struct sdw_bus_params *b_params = &m_rt->bus->params;
+
+ port_bo = t_data->block_offset;
+
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ rate = m_rt->stream->params.rate;
+ bps = m_rt->stream->params.bps;
+ sample_int = (m_rt->bus->params.curr_dr_freq / rate);
+ slave_total_ch = 0;
+
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ ch = sdw_ch_mask_to_ch(p_rt->ch_mask);
+
+ sdw_fill_xport_params(&p_rt->transport_params,
+ p_rt->num, false,
+ SDW_BLK_GRP_CNT_1,
+ sample_int, port_bo, port_bo >> 8,
+ t_data->hstart,
+ t_data->hstop,
+ (SDW_BLK_GRP_CNT_1 * ch), 0x0);
+
+ sdw_fill_port_params(&p_rt->port_params,
+ p_rt->num, bps,
+ SDW_PORT_FLOW_MODE_ISOCH,
+ b_params->s_data_mode);
+
+ port_bo += bps * ch;
+ slave_total_ch += ch;
+ }
+
+ if (m_rt->direction == SDW_DATA_DIR_TX &&
+ m_rt->ch_count == slave_total_ch) {
+ /*
+ * Slave devices were configured to access all channels
+ * of the stream, which indicates that they operate in
+ * 'mirror mode'. Make sure we reset the port offset for
+ * the next device in the list
+ */
+ port_bo = t_data->block_offset;
+ }
+ }
+}
+
+static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt,
+ struct sdw_group_params *params,
+ int port_bo, int hstop)
+{
+ struct sdw_transport_data t_data = {0};
+ struct sdw_port_runtime *p_rt;
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_bus_params *b_params = &bus->params;
+ int sample_int, hstart = 0;
+ unsigned int rate, bps, ch, no_ch;
+
+ rate = m_rt->stream->params.rate;
+ bps = m_rt->stream->params.bps;
+ ch = m_rt->ch_count;
+ sample_int = (bus->params.curr_dr_freq / rate);
+
+ if (rate != params->rate)
+ return;
+
+ t_data.hstop = hstop;
+ hstart = hstop - params->hwidth + 1;
+ t_data.hstart = hstart;
+
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ no_ch = sdw_ch_mask_to_ch(p_rt->ch_mask);
+
+ sdw_fill_xport_params(&p_rt->transport_params, p_rt->num,
+ false, SDW_BLK_GRP_CNT_1, sample_int,
+ port_bo, port_bo >> 8, hstart, hstop,
+ (SDW_BLK_GRP_CNT_1 * no_ch), 0x0);
+
+ sdw_fill_port_params(&p_rt->port_params,
+ p_rt->num, bps,
+ SDW_PORT_FLOW_MODE_ISOCH,
+ b_params->m_data_mode);
+
+ /* Check for first entry */
+ if (!(p_rt == list_first_entry(&m_rt->port_list,
+ struct sdw_port_runtime,
+ port_node))) {
+ port_bo += bps * ch;
+ continue;
+ }
+
+ t_data.hstart = hstart;
+ t_data.hstop = hstop;
+ t_data.block_offset = port_bo;
+ t_data.sub_block_offset = 0;
+ port_bo += bps * ch;
+ }
+
+ sdw_compute_slave_ports(m_rt, &t_data);
+}
+
+static void _sdw_compute_port_params(struct sdw_bus *bus,
+ struct sdw_group_params *params, int count)
+{
+ struct sdw_master_runtime *m_rt = NULL;
+ int hstop = bus->params.col - 1;
+ int block_offset, port_bo, i;
+
+ /* Run loop for all groups to compute transport parameters */
+ for (i = 0; i < count; i++) {
+ port_bo = 1;
+ block_offset = 1;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ sdw_compute_master_ports(m_rt, &params[i],
+ port_bo, hstop);
+
+ block_offset += m_rt->ch_count *
+ m_rt->stream->params.bps;
+ port_bo = block_offset;
+ }
+
+ hstop = hstop - params[i].hwidth;
+ }
+}
+
+static int sdw_compute_group_params(struct sdw_bus *bus,
+ struct sdw_group_params *params,
+ int *rates, int count)
+{
+ struct sdw_master_runtime *m_rt = NULL;
+ int sel_col = bus->params.col;
+ unsigned int rate, bps, ch;
+ int i, column_needed = 0;
+
+ /* Calculate bandwidth per group */
+ for (i = 0; i < count; i++) {
+ params[i].rate = rates[i];
+ params[i].full_bw = bus->params.curr_dr_freq / params[i].rate;
+ }
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ rate = m_rt->stream->params.rate;
+ bps = m_rt->stream->params.bps;
+ ch = m_rt->ch_count;
+
+ for (i = 0; i < count; i++) {
+ if (rate == params[i].rate)
+ params[i].payload_bw += bps * ch;
+ }
+ }
+
+ for (i = 0; i < count; i++) {
+ params[i].hwidth = (sel_col *
+ params[i].payload_bw + params[i].full_bw - 1) /
+ params[i].full_bw;
+
+ column_needed += params[i].hwidth;
+ }
+
+ if (column_needed > sel_col - 1)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int sdw_add_element_group_count(struct sdw_group *group,
+ unsigned int rate)
+{
+ int num = group->count;
+ int i;
+
+ for (i = 0; i <= num; i++) {
+ if (rate == group->rates[i])
+ break;
+
+ if (i != num)
+ continue;
+
+ if (group->count >= group->max_size) {
+ unsigned int *rates;
+
+ group->max_size += 1;
+ rates = krealloc(group->rates,
+ (sizeof(int) * group->max_size),
+ GFP_KERNEL);
+ if (!rates)
+ return -ENOMEM;
+ group->rates = rates;
+ }
+
+ group->rates[group->count++] = rate;
+ }
+
+ return 0;
+}
+
+static int sdw_get_group_count(struct sdw_bus *bus,
+ struct sdw_group *group)
+{
+ struct sdw_master_runtime *m_rt;
+ unsigned int rate;
+ int ret = 0;
+
+ group->count = 0;
+ group->max_size = SDW_STRM_RATE_GROUPING;
+ group->rates = kcalloc(group->max_size, sizeof(int), GFP_KERNEL);
+ if (!group->rates)
+ return -ENOMEM;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ rate = m_rt->stream->params.rate;
+ if (m_rt == list_first_entry(&bus->m_rt_list,
+ struct sdw_master_runtime,
+ bus_node)) {
+ group->rates[group->count++] = rate;
+
+ } else {
+ ret = sdw_add_element_group_count(group, rate);
+ if (ret < 0) {
+ kfree(group->rates);
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_compute_port_params: Compute transport and port parameters
+ *
+ * @bus: SDW Bus instance
+ */
+static int sdw_compute_port_params(struct sdw_bus *bus)
+{
+ struct sdw_group_params *params = NULL;
+ struct sdw_group group;
+ int ret;
+
+ ret = sdw_get_group_count(bus, &group);
+ if (ret < 0)
+ return ret;
+
+ if (group.count == 0)
+ goto out;
+
+ params = kcalloc(group.count, sizeof(*params), GFP_KERNEL);
+ if (!params) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* Compute transport parameters for grouped streams */
+ ret = sdw_compute_group_params(bus, params,
+ &group.rates[0], group.count);
+ if (ret < 0)
+ goto free_params;
+
+ _sdw_compute_port_params(bus, params, group.count);
+
+free_params:
+ kfree(params);
+out:
+ kfree(group.rates);
+
+ return ret;
+}
+
+static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq)
+{
+ struct sdw_master_prop *prop = &bus->prop;
+ int frame_int, frame_freq;
+ int r, c;
+
+ for (c = 0; c < SDW_FRAME_COLS; c++) {
+ for (r = 0; r < SDW_FRAME_ROWS; r++) {
+ if (sdw_rows[r] != prop->default_row ||
+ sdw_cols[c] != prop->default_col)
+ continue;
+
+ frame_int = sdw_rows[r] * sdw_cols[c];
+ frame_freq = clk_freq / frame_int;
+
+ if ((clk_freq - (frame_freq * SDW_FRAME_CTRL_BITS)) <
+ bus->params.bandwidth)
+ continue;
+
+ bus->params.row = sdw_rows[r];
+ bus->params.col = sdw_cols[c];
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * sdw_compute_bus_params: Compute bus parameters
+ *
+ * @bus: SDW Bus instance
+ */
+static int sdw_compute_bus_params(struct sdw_bus *bus)
+{
+ unsigned int max_dr_freq, curr_dr_freq = 0;
+ struct sdw_master_prop *mstr_prop = &bus->prop;
+ int i, clk_values, ret;
+ bool is_gear = false;
+ u32 *clk_buf;
+
+ if (mstr_prop->num_clk_gears) {
+ clk_values = mstr_prop->num_clk_gears;
+ clk_buf = mstr_prop->clk_gears;
+ is_gear = true;
+ } else if (mstr_prop->num_clk_freq) {
+ clk_values = mstr_prop->num_clk_freq;
+ clk_buf = mstr_prop->clk_freq;
+ } else {
+ clk_values = 1;
+ clk_buf = NULL;
+ }
+
+ max_dr_freq = mstr_prop->max_clk_freq * SDW_DOUBLE_RATE_FACTOR;
+
+ for (i = 0; i < clk_values; i++) {
+ if (!clk_buf)
+ curr_dr_freq = max_dr_freq;
+ else
+ curr_dr_freq = (is_gear) ?
+ (max_dr_freq >> clk_buf[i]) :
+ clk_buf[i] * SDW_DOUBLE_RATE_FACTOR;
+
+ if (curr_dr_freq <= bus->params.bandwidth)
+ continue;
+
+ break;
+
+ /*
+ * TODO: Check all the Slave(s) port(s) audio modes and find
+ * whether given clock rate is supported with glitchless
+ * transition.
+ */
+ }
+
+ if (i == clk_values)
+ return -EINVAL;
+
+ ret = sdw_select_row_col(bus, curr_dr_freq);
+ if (ret < 0)
+ return -EINVAL;
+
+ bus->params.curr_dr_freq = curr_dr_freq;
+ return 0;
+}
+
+/**
+ * sdw_compute_params: Compute bus, transport and port parameters
+ *
+ * @bus: SDW Bus instance
+ */
+int sdw_compute_params(struct sdw_bus *bus)
+{
+ int ret;
+
+ /* Computes clock frequency, frame shape and frame frequency */
+ ret = sdw_compute_bus_params(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Compute bus params failed: %d", ret);
+ return ret;
+ }
+
+ /* Compute transport and port params */
+ ret = sdw_compute_port_params(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Compute transport params failed: %d", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_compute_params);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("SoundWire Generic Bandwidth Allocation");
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c
index a283670659a9..6a1e862b16c3 100644
--- a/drivers/soundwire/intel.c
+++ b/drivers/soundwire/intel.c
@@ -22,6 +22,24 @@
#include "bus.h"
#include "intel.h"
+#define INTEL_MASTER_SUSPEND_DELAY_MS 3000
+
+/*
+ * debug/config flags for the Intel SoundWire Master.
+ *
+ * Since we may have multiple masters active, we can have up to 8
+ * flags reused in each byte, with master0 using the ls-byte, etc.
+ */
+
+#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0)
+#define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1)
+#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE BIT(2)
+#define SDW_INTEL_MASTER_DISABLE_MULTI_LINK BIT(3)
+
+static int md_flags;
+module_param_named(sdw_md_flags, md_flags, int, 0444);
+MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)");
+
/* Intel SHIM Registers Definition */
#define SDW_SHIM_LCAP 0x0
#define SDW_SHIM_LCTL 0x4
@@ -45,7 +63,9 @@
#define SDW_SHIM_WAKESTS 0x192
#define SDW_SHIM_LCTL_SPA BIT(0)
+#define SDW_SHIM_LCTL_SPA_MASK GENMASK(3, 0)
#define SDW_SHIM_LCTL_CPA BIT(8)
+#define SDW_SHIM_LCTL_CPA_MASK GENMASK(11, 8)
#define SDW_SHIM_SYNC_SYNCPRD_VAL_24 (24000 / SDW_CADENCE_GSYNC_KHZ - 1)
#define SDW_SHIM_SYNC_SYNCPRD_VAL_38_4 (38400 / SDW_CADENCE_GSYNC_KHZ - 1)
@@ -242,6 +262,42 @@ static int intel_reg_show(struct seq_file *s_file, void *data)
}
DEFINE_SHOW_ATTRIBUTE(intel_reg);
+static int intel_set_m_datamode(void *data, u64 value)
+{
+ struct sdw_intel *sdw = data;
+ struct sdw_bus *bus = &sdw->cdns.bus;
+
+ if (value > SDW_PORT_DATA_MODE_STATIC_1)
+ return -EINVAL;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ bus->params.m_data_mode = value;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(intel_set_m_datamode_fops, NULL,
+ intel_set_m_datamode, "%llu\n");
+
+static int intel_set_s_datamode(void *data, u64 value)
+{
+ struct sdw_intel *sdw = data;
+ struct sdw_bus *bus = &sdw->cdns.bus;
+
+ if (value > SDW_PORT_DATA_MODE_STATIC_1)
+ return -EINVAL;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ bus->params.s_data_mode = value;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(intel_set_s_datamode_fops, NULL,
+ intel_set_s_datamode, "%llu\n");
+
static void intel_debugfs_init(struct sdw_intel *sdw)
{
struct dentry *root = sdw->cdns.bus.debugfs;
@@ -254,6 +310,12 @@ static void intel_debugfs_init(struct sdw_intel *sdw)
debugfs_create_file("intel-registers", 0400, sdw->debugfs, sdw,
&intel_reg_fops);
+ debugfs_create_file("intel-m-datamode", 0200, sdw->debugfs, sdw,
+ &intel_set_m_datamode_fops);
+
+ debugfs_create_file("intel-s-datamode", 0200, sdw->debugfs, sdw,
+ &intel_set_s_datamode_fops);
+
sdw_cdns_debugfs_init(&sdw->cdns, sdw->debugfs);
}
@@ -277,8 +339,8 @@ static int intel_link_power_up(struct sdw_intel *sdw)
u32 *shim_mask = sdw->link_res->shim_mask;
struct sdw_bus *bus = &sdw->cdns.bus;
struct sdw_master_prop *prop = &bus->prop;
- int spa_mask, cpa_mask;
- int link_control;
+ u32 spa_mask, cpa_mask;
+ u32 link_control;
int ret = 0;
u32 syncprd;
u32 sync_reg;
@@ -301,33 +363,35 @@ static int intel_link_power_up(struct sdw_intel *sdw)
syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24;
if (!*shim_mask) {
+ dev_dbg(sdw->cdns.dev, "%s: powering up all links\n", __func__);
+
/* we first need to program the SyncPRD/CPU registers */
dev_dbg(sdw->cdns.dev,
"%s: first link up, programming SYNCPRD\n", __func__);
/* set SyncPRD period */
sync_reg = intel_readl(shim, SDW_SHIM_SYNC);
- sync_reg |= (syncprd <<
- SDW_REG_SHIFT(SDW_SHIM_SYNC_SYNCPRD));
+ u32p_replace_bits(&sync_reg, syncprd, SDW_SHIM_SYNC_SYNCPRD);
/* Set SyncCPU bit */
sync_reg |= SDW_SHIM_SYNC_SYNCCPU;
intel_writel(shim, SDW_SHIM_SYNC, sync_reg);
- }
- /* Link power up sequence */
- link_control = intel_readl(shim, SDW_SHIM_LCTL);
- spa_mask = (SDW_SHIM_LCTL_SPA << link_id);
- cpa_mask = (SDW_SHIM_LCTL_CPA << link_id);
- link_control |= spa_mask;
+ /* Link power up sequence */
+ link_control = intel_readl(shim, SDW_SHIM_LCTL);
- ret = intel_set_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask);
- if (ret < 0) {
- dev_err(sdw->cdns.dev, "Failed to power up link: %d\n", ret);
- goto out;
- }
+ /* only power-up enabled links */
+ spa_mask = FIELD_PREP(SDW_SHIM_LCTL_SPA_MASK, sdw->link_res->link_mask);
+ cpa_mask = FIELD_PREP(SDW_SHIM_LCTL_CPA_MASK, sdw->link_res->link_mask);
+
+ link_control |= spa_mask;
+
+ ret = intel_set_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "Failed to power up link: %d\n", ret);
+ goto out;
+ }
- if (!*shim_mask) {
/* SyncCPU will change once link is active */
ret = intel_wait_bit(shim, SDW_SHIM_SYNC,
SDW_SHIM_SYNC_SYNCCPU, 0);
@@ -426,7 +490,7 @@ static int intel_shim_init(struct sdw_intel *sdw, bool clock_stop)
intel_shim_glue_to_master_ip(sdw);
- act |= 0x1 << SDW_REG_SHIFT(SDW_SHIM_CTMCTL_DOAIS);
+ u16p_replace_bits(&act, 0x1, SDW_SHIM_CTMCTL_DOAIS);
act |= SDW_SHIM_CTMCTL_DACTQE;
act |= SDW_SHIM_CTMCTL_DODS;
intel_writew(shim, SDW_SHIM_CTMCTL(link_id), act);
@@ -463,9 +527,9 @@ static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
mutex_unlock(sdw->link_res->shim_lock);
}
-static int __maybe_unused intel_link_power_down(struct sdw_intel *sdw)
+static int intel_link_power_down(struct sdw_intel *sdw)
{
- int link_control, spa_mask, cpa_mask;
+ u32 link_control, spa_mask, cpa_mask;
unsigned int link_id = sdw->instance;
void __iomem *shim = sdw->link_res->shim;
u32 *shim_mask = sdw->link_res->shim_mask;
@@ -475,24 +539,37 @@ static int __maybe_unused intel_link_power_down(struct sdw_intel *sdw)
intel_shim_master_ip_to_glue(sdw);
- /* Link power down sequence */
- link_control = intel_readl(shim, SDW_SHIM_LCTL);
- spa_mask = ~(SDW_SHIM_LCTL_SPA << link_id);
- cpa_mask = (SDW_SHIM_LCTL_CPA << link_id);
- link_control &= spa_mask;
-
- ret = intel_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask);
-
if (!(*shim_mask & BIT(link_id)))
dev_err(sdw->cdns.dev,
"%s: Unbalanced power-up/down calls\n", __func__);
*shim_mask &= ~BIT(link_id);
+ if (!*shim_mask) {
+
+ dev_dbg(sdw->cdns.dev, "%s: powering down all links\n", __func__);
+
+ /* Link power down sequence */
+ link_control = intel_readl(shim, SDW_SHIM_LCTL);
+
+ /* only power-down enabled links */
+ spa_mask = FIELD_PREP(SDW_SHIM_LCTL_SPA_MASK, ~sdw->link_res->link_mask);
+ cpa_mask = FIELD_PREP(SDW_SHIM_LCTL_CPA_MASK, sdw->link_res->link_mask);
+
+ link_control &= spa_mask;
+
+ ret = intel_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask);
+ }
+
+ link_control = intel_readl(shim, SDW_SHIM_LCTL);
+
mutex_unlock(sdw->link_res->shim_lock);
- if (ret < 0)
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: could not power down link\n", __func__);
+
return ret;
+ }
sdw->cdns.link_up = false;
return 0;
@@ -538,6 +615,19 @@ static int intel_shim_sync_go_unlocked(struct sdw_intel *sdw)
return ret;
}
+static int intel_shim_sync_go(struct sdw_intel *sdw)
+{
+ int ret;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ ret = intel_shim_sync_go_unlocked(sdw);
+
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ return ret;
+}
+
/*
* PDI routines
*/
@@ -551,12 +641,9 @@ static void intel_pdi_init(struct sdw_intel *sdw,
/* PCM Stream Capability */
pcm_cap = intel_readw(shim, SDW_SHIM_PCMSCAP(link_id));
- config->pcm_bd = (pcm_cap & SDW_SHIM_PCMSCAP_BSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_BSS);
- config->pcm_in = (pcm_cap & SDW_SHIM_PCMSCAP_ISS) >>
- SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_ISS);
- config->pcm_out = (pcm_cap & SDW_SHIM_PCMSCAP_OSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_OSS);
+ config->pcm_bd = FIELD_GET(SDW_SHIM_PCMSCAP_BSS, pcm_cap);
+ config->pcm_in = FIELD_GET(SDW_SHIM_PCMSCAP_ISS, pcm_cap);
+ config->pcm_out = FIELD_GET(SDW_SHIM_PCMSCAP_OSS, pcm_cap);
dev_dbg(sdw->cdns.dev, "PCM cap bd:%d in:%d out:%d\n",
config->pcm_bd, config->pcm_in, config->pcm_out);
@@ -564,12 +651,9 @@ static void intel_pdi_init(struct sdw_intel *sdw,
/* PDM Stream Capability */
pdm_cap = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id));
- config->pdm_bd = (pdm_cap & SDW_SHIM_PDMSCAP_BSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_BSS);
- config->pdm_in = (pdm_cap & SDW_SHIM_PDMSCAP_ISS) >>
- SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_ISS);
- config->pdm_out = (pdm_cap & SDW_SHIM_PDMSCAP_OSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_OSS);
+ config->pdm_bd = FIELD_GET(SDW_SHIM_PDMSCAP_BSS, pdm_cap);
+ config->pdm_in = FIELD_GET(SDW_SHIM_PDMSCAP_ISS, pdm_cap);
+ config->pdm_out = FIELD_GET(SDW_SHIM_PDMSCAP_OSS, pdm_cap);
dev_dbg(sdw->cdns.dev, "PDM cap bd:%d in:%d out:%d\n",
config->pdm_bd, config->pdm_in, config->pdm_out);
@@ -596,8 +680,7 @@ intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm)
} else {
count = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id));
- count = ((count & SDW_SHIM_PDMSCAP_CPSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_CPSS));
+ count = FIELD_GET(SDW_SHIM_PDMSCAP_CPSS, count);
}
/* zero based values for channel count in register */
@@ -671,10 +754,9 @@ intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
else
pdi_conf &= ~(SDW_SHIM_PCMSYCM_DIR);
- pdi_conf |= (pdi->intel_alh_id <<
- SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_STREAM));
- pdi_conf |= (pdi->l_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_LCHN));
- pdi_conf |= (pdi->h_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_HCHN));
+ u32p_replace_bits(&pdi_conf, pdi->intel_alh_id, SDW_SHIM_PCMSYCM_STREAM);
+ u32p_replace_bits(&pdi_conf, pdi->l_ch_num, SDW_SHIM_PCMSYCM_LCHN);
+ u32p_replace_bits(&pdi_conf, pdi->h_ch_num, SDW_SHIM_PCMSYCM_HCHN);
intel_writew(shim, SDW_SHIM_PCMSYCHM(link_id, pdi->num), pdi_conf);
}
@@ -694,11 +776,8 @@ intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
/* Program Stream config ALH register */
conf = intel_readl(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id));
- conf |= (SDW_ALH_STRMZCFG_DMAT_VAL <<
- SDW_REG_SHIFT(SDW_ALH_STRMZCFG_DMAT));
-
- conf |= ((pdi->ch_count - 1) <<
- SDW_REG_SHIFT(SDW_ALH_STRMZCFG_CHN));
+ u32p_replace_bits(&conf, SDW_ALH_STRMZCFG_DMAT_VAL, SDW_ALH_STRMZCFG_DMAT);
+ u32p_replace_bits(&conf, pdi->ch_count - 1, SDW_ALH_STRMZCFG_CHN);
intel_writel(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id), conf);
}
@@ -807,10 +886,17 @@ unlock:
static int intel_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- /*
- * TODO: add pm_runtime support here, the startup callback
- * will make sure the IP is 'active'
- */
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ ret = pm_runtime_get_sync(cdns->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(cdns->dev);
+ return ret;
+ }
return 0;
}
@@ -856,6 +942,10 @@ static int intel_hw_params(struct snd_pcm_substream *substream,
intel_pdi_alh_configure(sdw, pdi);
sdw_cdns_config_stream(cdns, ch, dir, pdi);
+ /* store pdi and hw_params, may be needed in prepare step */
+ dma->suspended = false;
+ dma->pdi = pdi;
+ dma->hw_params = params;
/* Inform DSP about PDI stream number */
ret = intel_params_stream(sdw, substream, dai, params,
@@ -899,7 +989,11 @@ error:
static int intel_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_cdns_dma_data *dma;
+ int ch, dir;
+ int ret = 0;
dma = snd_soc_dai_get_dma_data(dai, substream);
if (!dma) {
@@ -908,43 +1002,35 @@ static int intel_prepare(struct snd_pcm_substream *substream,
return -EIO;
}
- return sdw_prepare_stream(dma->stream);
-}
+ if (dma->suspended) {
+ dma->suspended = false;
-static int intel_trigger(struct snd_pcm_substream *substream, int cmd,
- struct snd_soc_dai *dai)
-{
- struct sdw_cdns_dma_data *dma;
- int ret;
-
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma) {
- dev_err(dai->dev, "failed to get dma data in %s", __func__);
- return -EIO;
- }
+ /*
+ * .prepare() is called after system resume, where we
+ * need to reinitialize the SHIM/ALH/Cadence IP.
+ * .prepare() is also called to deal with underflows,
+ * but in those cases we cannot touch ALH/SHIM
+ * registers
+ */
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- case SNDRV_PCM_TRIGGER_RESUME:
- ret = sdw_enable_stream(dma->stream);
- break;
+ /* configure stream */
+ ch = params_channels(dma->hw_params);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dir = SDW_DATA_DIR_RX;
+ else
+ dir = SDW_DATA_DIR_TX;
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_STOP:
- ret = sdw_disable_stream(dma->stream);
- break;
+ intel_pdi_shim_configure(sdw, dma->pdi);
+ intel_pdi_alh_configure(sdw, dma->pdi);
+ sdw_cdns_config_stream(cdns, ch, dir, dma->pdi);
- default:
- ret = -EINVAL;
- break;
+ /* Inform DSP about PDI stream number */
+ ret = intel_params_stream(sdw, substream, dai,
+ dma->hw_params,
+ sdw->instance,
+ dma->pdi->intel_alh_id);
}
- if (ret)
- dev_err(dai->dev,
- "%s trigger %d failed: %d",
- __func__, cmd, ret);
return ret;
}
@@ -960,12 +1046,12 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
if (!dma)
return -EIO;
- ret = sdw_deprepare_stream(dma->stream);
- if (ret) {
- dev_err(dai->dev, "sdw_deprepare_stream: failed %d", ret);
- return ret;
- }
-
+ /*
+ * The sdw stream state will transition to RELEASED when stream->
+ * master_list is empty. So the stream state will transition to
+ * DEPREPARED for the first cpu-dai and to RELEASED for the last
+ * cpu-dai.
+ */
ret = sdw_stream_remove_master(&cdns->bus, dma->stream);
if (ret < 0) {
dev_err(dai->dev, "remove master from stream %s failed: %d\n",
@@ -979,13 +1065,42 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
return ret;
}
+ dma->hw_params = NULL;
+ dma->pdi = NULL;
+
return 0;
}
static void intel_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ pm_runtime_mark_last_busy(cdns->dev);
+ pm_runtime_put_autosuspend(cdns->dev);
+}
+
+static int intel_component_dais_suspend(struct snd_soc_component *component)
+{
+ struct sdw_cdns_dma_data *dma;
+ struct snd_soc_dai *dai;
+
+ for_each_component_dais(component, dai) {
+ /*
+ * we don't have a .suspend dai_ops, and we don't have access
+ * to the substream, so let's mark both capture and playback
+ * DMA contexts as suspended
+ */
+ dma = dai->playback_dma_data;
+ if (dma)
+ dma->suspended = true;
+
+ dma = dai->capture_dma_data;
+ if (dma)
+ dma->suspended = true;
+ }
+
+ return 0;
}
static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai,
@@ -1011,7 +1126,7 @@ static void *intel_get_sdw_stream(struct snd_soc_dai *dai,
dma = dai->capture_dma_data;
if (!dma)
- return NULL;
+ return ERR_PTR(-EINVAL);
return dma->stream;
}
@@ -1020,7 +1135,6 @@ static const struct snd_soc_dai_ops intel_pcm_dai_ops = {
.startup = intel_startup,
.hw_params = intel_hw_params,
.prepare = intel_prepare,
- .trigger = intel_trigger,
.hw_free = intel_hw_free,
.shutdown = intel_shutdown,
.set_sdw_stream = intel_pcm_set_sdw_stream,
@@ -1031,7 +1145,6 @@ static const struct snd_soc_dai_ops intel_pdm_dai_ops = {
.startup = intel_startup,
.hw_params = intel_hw_params,
.prepare = intel_prepare,
- .trigger = intel_trigger,
.hw_free = intel_hw_free,
.shutdown = intel_shutdown,
.set_sdw_stream = intel_pdm_set_sdw_stream,
@@ -1040,6 +1153,7 @@ static const struct snd_soc_dai_ops intel_pdm_dai_ops = {
static const struct snd_soc_component_driver dai_component = {
.name = "soundwire",
+ .suspend = intel_component_dais_suspend
};
static int intel_create_dai(struct sdw_cdns *cdns,
@@ -1207,10 +1321,7 @@ static int intel_init(struct sdw_intel *sdw)
intel_shim_init(sdw, clock_stop);
- if (clock_stop)
- return 0;
-
- return sdw_cdns_init(&sdw->cdns);
+ return 0;
}
/*
@@ -1249,6 +1360,9 @@ static int intel_master_probe(struct platform_device *pdev)
/* set driver data, accessed by snd_soc_dai_get_drvdata() */
dev_set_drvdata(dev, cdns);
+ /* use generic bandwidth allocation algorithm */
+ sdw->cdns.bus.compute_params = sdw_compute_params;
+
ret = sdw_bus_master_add(bus, dev, dev->fwnode);
if (ret) {
dev_err(dev, "sdw_bus_master_add fail: %d\n", ret);
@@ -1259,6 +1373,11 @@ static int intel_master_probe(struct platform_device *pdev)
dev_info(dev,
"SoundWire master %d is disabled, will be ignored\n",
bus->link_id);
+ /*
+ * Ignore BIOS err_threshold, it's a really bad idea when dealing
+ * with multiple hardware synchronized links
+ */
+ bus->prop.err_threshold = 0;
return 0;
}
@@ -1270,6 +1389,9 @@ int intel_master_startup(struct platform_device *pdev)
struct sdw_cdns *cdns = dev_get_drvdata(dev);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
+ int link_flags;
+ bool multi_link;
+ u32 clock_stop_quirks;
int ret;
if (bus->prop.hw_disabled) {
@@ -1279,7 +1401,23 @@ int intel_master_startup(struct platform_device *pdev)
return 0;
}
- /* Initialize shim, controller and Cadence IP */
+ link_flags = md_flags >> (bus->link_id * 8);
+ multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK);
+ if (!multi_link) {
+ dev_dbg(dev, "Multi-link is disabled\n");
+ bus->multi_link = false;
+ } else {
+ /*
+ * hardware-based synchronization is required regardless
+ * of the number of segments used by a stream: SSP-based
+ * synchronization is gated by gsync when the multi-master
+ * mode is set.
+ */
+ bus->multi_link = true;
+ bus->hw_sync_min_links = 1;
+ }
+
+ /* Initialize shim, controller */
ret = intel_init(sdw);
if (ret)
goto err_init;
@@ -1298,12 +1436,33 @@ int intel_master_startup(struct platform_device *pdev)
goto err_init;
}
+ /*
+ * follow recommended programming flows to avoid timeouts when
+ * gsync is enabled
+ */
+ if (multi_link)
+ intel_shim_sync_arm(sdw);
+
+ ret = sdw_cdns_init(cdns);
+ if (ret < 0) {
+ dev_err(dev, "unable to initialize Cadence IP\n");
+ goto err_interrupt;
+ }
+
ret = sdw_cdns_exit_reset(cdns);
if (ret < 0) {
dev_err(dev, "unable to exit bus reset sequence\n");
goto err_interrupt;
}
+ if (multi_link) {
+ ret = intel_shim_sync_go(sdw);
+ if (ret < 0) {
+ dev_err(dev, "sync go failed: %d\n", ret);
+ goto err_interrupt;
+ }
+ }
+
/* Register DAIs */
ret = intel_register_dai(sdw);
if (ret) {
@@ -1314,6 +1473,47 @@ int intel_master_startup(struct platform_device *pdev)
intel_debugfs_init(sdw);
+ /* Enable runtime PM */
+ if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME)) {
+ pm_runtime_set_autosuspend_delay(dev,
+ INTEL_MASTER_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_mark_last_busy(dev);
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ }
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+ if (clock_stop_quirks & SDW_INTEL_CLK_STOP_NOT_ALLOWED) {
+ /*
+ * To keep the clock running we need to prevent
+ * pm_runtime suspend from happening by increasing the
+ * reference count.
+ * This quirk is specified by the parent PCI device in
+ * case of specific latency requirements. It will have
+ * no effect if pm_runtime is disabled by the user via
+ * a module parameter for testing purposes.
+ */
+ pm_runtime_get_noresume(dev);
+ }
+
+ /*
+ * The runtime PM status of Slave devices is "Unsupported"
+ * until they report as ATTACHED. If they don't, e.g. because
+ * there are no Slave devices populated or if the power-on is
+ * delayed or dependent on a power switch, the Master will
+ * remain active and prevent its parent from suspending.
+ *
+ * Conditionally force the pm_runtime core to re-evaluate the
+ * Master status in the absence of any Slave activity. A quirk
+ * is provided to e.g. deal with Slaves that may be powered on
+ * with a delay. A more complete solution would require the
+ * definition of Master properties.
+ */
+ if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE))
+ pm_runtime_idle(dev);
+
return 0;
err_interrupt:
@@ -1329,6 +1529,11 @@ static int intel_master_remove(struct platform_device *pdev)
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
+ /*
+ * Since pm_runtime is already disabled, we don't decrease
+ * the refcount when the clock_stop_quirk is
+ * SDW_INTEL_CLK_STOP_NOT_ALLOWED
+ */
if (!bus->prop.hw_disabled) {
intel_debugfs_exit(sdw);
sdw_cdns_enable_interrupt(cdns, false);
@@ -1376,12 +1581,408 @@ int intel_master_process_wakeen_event(struct platform_device *pdev)
return 0;
}
+/*
+ * PM calls
+ */
+
+#ifdef CONFIG_PM
+
+static int __maybe_unused intel_suspend(struct device *dev)
+{
+ struct sdw_cdns *cdns = dev_get_drvdata(dev);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_bus *bus = &cdns->bus;
+ u32 clock_stop_quirks;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ if (pm_runtime_suspended(dev)) {
+ dev_dbg(dev, "%s: pm_runtime status: suspended\n", __func__);
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+
+ if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET ||
+ !clock_stop_quirks) &&
+ !pm_runtime_suspended(dev->parent)) {
+
+ /*
+ * if we've enabled clock stop, and the parent
+ * is still active, disable shim wake. The
+ * SHIM registers are not accessible if the
+ * parent is already pm_runtime suspended so
+ * it's too late to change that configuration
+ */
+
+ intel_shim_wake(sdw, false);
+ }
+
+ return 0;
+ }
+
+ ret = sdw_cdns_enable_interrupt(cdns, false);
+ if (ret < 0) {
+ dev_err(dev, "cannot disable interrupts on suspend\n");
+ return ret;
+ }
+
+ ret = intel_link_power_down(sdw);
+ if (ret) {
+ dev_err(dev, "Link power down failed: %d", ret);
+ return ret;
+ }
+
+ intel_shim_wake(sdw, false);
+
+ return 0;
+}
+
+static int intel_suspend_runtime(struct device *dev)
+{
+ struct sdw_cdns *cdns = dev_get_drvdata(dev);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_bus *bus = &cdns->bus;
+ u32 clock_stop_quirks;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+
+ if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
+
+ ret = sdw_cdns_enable_interrupt(cdns, false);
+ if (ret < 0) {
+ dev_err(dev, "cannot disable interrupts on suspend\n");
+ return ret;
+ }
+
+ ret = intel_link_power_down(sdw);
+ if (ret) {
+ dev_err(dev, "Link power down failed: %d", ret);
+ return ret;
+ }
+
+ intel_shim_wake(sdw, false);
+
+ } else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET ||
+ !clock_stop_quirks) {
+ ret = sdw_cdns_clock_stop(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "cannot enable clock stop on suspend\n");
+ return ret;
+ }
+
+ ret = sdw_cdns_enable_interrupt(cdns, false);
+ if (ret < 0) {
+ dev_err(dev, "cannot disable interrupts on suspend\n");
+ return ret;
+ }
+
+ ret = intel_link_power_down(sdw);
+ if (ret) {
+ dev_err(dev, "Link power down failed: %d", ret);
+ return ret;
+ }
+
+ intel_shim_wake(sdw, true);
+ } else {
+ dev_err(dev, "%s clock_stop_quirks %x unsupported\n",
+ __func__, clock_stop_quirks);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int __maybe_unused intel_resume(struct device *dev)
+{
+ struct sdw_cdns *cdns = dev_get_drvdata(dev);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_bus *bus = &cdns->bus;
+ int link_flags;
+ bool multi_link;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ link_flags = md_flags >> (bus->link_id * 8);
+ multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK);
+
+ if (pm_runtime_suspended(dev)) {
+ dev_dbg(dev, "%s: pm_runtime status was suspended, forcing active\n", __func__);
+
+ /* follow required sequence from runtime_pm.rst */
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_enable(dev);
+
+ link_flags = md_flags >> (bus->link_id * 8);
+
+ if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE))
+ pm_runtime_idle(dev);
+ }
+
+ ret = intel_init(sdw);
+ if (ret) {
+ dev_err(dev, "%s failed: %d", __func__, ret);
+ return ret;
+ }
+
+ /*
+ * make sure all Slaves are tagged as UNATTACHED and provide
+ * reason for reinitialization
+ */
+ sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
+
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "cannot enable interrupts during resume\n");
+ return ret;
+ }
+
+ /*
+ * follow recommended programming flows to avoid timeouts when
+ * gsync is enabled
+ */
+ if (multi_link)
+ intel_shim_sync_arm(sdw);
+
+ ret = sdw_cdns_init(&sdw->cdns);
+ if (ret < 0) {
+ dev_err(dev, "unable to initialize Cadence IP during resume\n");
+ return ret;
+ }
+
+ ret = sdw_cdns_exit_reset(cdns);
+ if (ret < 0) {
+ dev_err(dev, "unable to exit bus reset sequence during resume\n");
+ return ret;
+ }
+
+ if (multi_link) {
+ ret = intel_shim_sync_go(sdw);
+ if (ret < 0) {
+ dev_err(dev, "sync go failed during resume\n");
+ return ret;
+ }
+ }
+
+ /*
+ * after system resume, the pm_runtime suspend() may kick in
+ * during the enumeration, before any children device force the
+ * master device to remain active. Using pm_runtime_get()
+ * routines is not really possible, since it'd prevent the
+ * master from suspending.
+ * A reasonable compromise is to update the pm_runtime
+ * counters and delay the pm_runtime suspend by several
+ * seconds, by when all enumeration should be complete.
+ */
+ pm_runtime_mark_last_busy(dev);
+
+ return ret;
+}
+
+static int intel_resume_runtime(struct device *dev)
+{
+ struct sdw_cdns *cdns = dev_get_drvdata(dev);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_bus *bus = &cdns->bus;
+ u32 clock_stop_quirks;
+ bool clock_stop0;
+ int link_flags;
+ bool multi_link;
+ int status;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ link_flags = md_flags >> (bus->link_id * 8);
+ multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK);
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+
+ if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
+ ret = intel_init(sdw);
+ if (ret) {
+ dev_err(dev, "%s failed: %d", __func__, ret);
+ return ret;
+ }
+
+ /*
+ * make sure all Slaves are tagged as UNATTACHED and provide
+ * reason for reinitialization
+ */
+ sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
+
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "cannot enable interrupts during resume\n");
+ return ret;
+ }
+
+ /*
+ * follow recommended programming flows to avoid
+ * timeouts when gsync is enabled
+ */
+ if (multi_link)
+ intel_shim_sync_arm(sdw);
+
+ ret = sdw_cdns_init(&sdw->cdns);
+ if (ret < 0) {
+ dev_err(dev, "unable to initialize Cadence IP during resume\n");
+ return ret;
+ }
+
+ ret = sdw_cdns_exit_reset(cdns);
+ if (ret < 0) {
+ dev_err(dev, "unable to exit bus reset sequence during resume\n");
+ return ret;
+ }
+
+ if (multi_link) {
+ ret = intel_shim_sync_go(sdw);
+ if (ret < 0) {
+ dev_err(dev, "sync go failed during resume\n");
+ return ret;
+ }
+ }
+ } else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) {
+ ret = intel_init(sdw);
+ if (ret) {
+ dev_err(dev, "%s failed: %d", __func__, ret);
+ return ret;
+ }
+
+ /*
+ * An exception condition occurs for the CLK_STOP_BUS_RESET
+ * case if one or more masters remain active. In this condition,
+ * all the masters are powered on for they are in the same power
+ * domain. Master can preserve its context for clock stop0, so
+ * there is no need to clear slave status and reset bus.
+ */
+ clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
+
+ if (!clock_stop0) {
+
+ /*
+ * make sure all Slaves are tagged as UNATTACHED and
+ * provide reason for reinitialization
+ */
+
+ status = SDW_UNATTACH_REQUEST_MASTER_RESET;
+ sdw_clear_slave_status(bus, status);
+
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "cannot enable interrupts during resume\n");
+ return ret;
+ }
+
+ /*
+ * follow recommended programming flows to avoid
+ * timeouts when gsync is enabled
+ */
+ if (multi_link)
+ intel_shim_sync_arm(sdw);
+
+ /*
+ * Re-initialize the IP since it was powered-off
+ */
+ sdw_cdns_init(&sdw->cdns);
+
+ } else {
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "cannot enable interrupts during resume\n");
+ return ret;
+ }
+ }
+
+ ret = sdw_cdns_clock_restart(cdns, !clock_stop0);
+ if (ret < 0) {
+ dev_err(dev, "unable to restart clock during resume\n");
+ return ret;
+ }
+
+ if (!clock_stop0) {
+ ret = sdw_cdns_exit_reset(cdns);
+ if (ret < 0) {
+ dev_err(dev, "unable to exit bus reset sequence during resume\n");
+ return ret;
+ }
+
+ if (multi_link) {
+ ret = intel_shim_sync_go(sdw);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "sync go failed during resume\n");
+ return ret;
+ }
+ }
+ }
+ } else if (!clock_stop_quirks) {
+
+ clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
+ if (!clock_stop0)
+ dev_err(dev, "%s invalid configuration, clock was not stopped", __func__);
+
+ ret = intel_init(sdw);
+ if (ret) {
+ dev_err(dev, "%s failed: %d", __func__, ret);
+ return ret;
+ }
+
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "cannot enable interrupts during resume\n");
+ return ret;
+ }
+
+ ret = sdw_cdns_clock_restart(cdns, false);
+ if (ret < 0) {
+ dev_err(dev, "unable to resume master during resume\n");
+ return ret;
+ }
+ } else {
+ dev_err(dev, "%s clock_stop_quirks %x unsupported\n",
+ __func__, clock_stop_quirks);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+#endif
+
+static const struct dev_pm_ops intel_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume)
+ SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL)
+};
+
static struct platform_driver sdw_intel_drv = {
.probe = intel_master_probe,
.remove = intel_master_remove,
.driver = {
.name = "intel-sdw",
- },
+ .pm = &intel_pm,
+ }
};
module_platform_driver(sdw_intel_drv);
diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h
index 4ea3d262d249..76820d0b9deb 100644
--- a/drivers/soundwire/intel.h
+++ b/drivers/soundwire/intel.h
@@ -17,6 +17,8 @@
* @dev: device implementing hw_params and free callbacks
* @shim_lock: mutex to handle access to shared SHIM registers
* @shim_mask: global pointer to check SHIM register initialization
+ * @clock_stop_quirks: mask defining requested behavior on pm_suspend
+ * @link_mask: global mask needed for power-up/down sequences
* @cdns: Cadence master descriptor
* @list: used to walk-through all masters exposed by the same controller
*/
@@ -31,6 +33,8 @@ struct sdw_intel_link_res {
struct device *dev;
struct mutex *shim_lock; /* protect shared registers */
u32 *shim_mask;
+ u32 clock_stop_quirks;
+ u32 link_mask;
struct sdw_cdns *cdns;
struct list_head list;
};
diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c
index 047252a91c9e..cabdadb09a1b 100644
--- a/drivers/soundwire/intel_init.c
+++ b/drivers/soundwire/intel_init.c
@@ -13,6 +13,7 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/soundwire/sdw_intel.h>
#include "cadence_master.h"
#include "intel.h"
@@ -68,8 +69,13 @@ static int sdw_intel_cleanup(struct sdw_intel_ctx *ctx)
if (!(link_mask & BIT(i)))
continue;
- if (link->pdev)
+ if (link->pdev) {
+ pm_runtime_disable(&link->pdev->dev);
platform_device_unregister(link->pdev);
+ }
+
+ if (!link->clock_stop_quirks)
+ pm_runtime_put_noidle(link->dev);
}
return 0;
@@ -246,8 +252,10 @@ static struct sdw_intel_ctx
link->ops = res->ops;
link->dev = res->dev;
+ link->clock_stop_quirks = res->clock_stop_quirks;
link->shim_lock = &ctx->shim_lock;
link->shim_mask = &ctx->shim_mask;
+ link->link_mask = link_mask;
memset(&pdevinfo, 0, sizeof(pdevinfo));
@@ -334,6 +342,16 @@ sdw_intel_startup_controller(struct sdw_intel_ctx *ctx)
continue;
intel_master_startup(link->pdev);
+
+ if (!link->clock_stop_quirks) {
+ /*
+ * we need to prevent the parent PCI device
+ * from entering pm_runtime suspend, so that
+ * power rails to the SoundWire IP are not
+ * turned off.
+ */
+ pm_runtime_get_noresume(link->dev);
+ }
}
return 0;
@@ -365,7 +383,7 @@ static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level,
* Name(_ADR, 0x40000000), with bits 31..28 representing the
* SoundWire link so filter accordingly
*/
- if ((adr & GENMASK(31, 28)) >> 28 != SDW_LINK_TYPE)
+ if (FIELD_GET(GENMASK(31, 28), adr) != SDW_LINK_TYPE)
return AE_OK; /* keep going */
/* device found, stop namespace walk */
diff --git a/drivers/soundwire/master.c b/drivers/soundwire/master.c
index 5f0b2189defe..3488bb824e84 100644
--- a/drivers/soundwire/master.c
+++ b/drivers/soundwire/master.c
@@ -154,6 +154,7 @@ int sdw_master_device_add(struct sdw_bus *bus, struct device *parent,
bus->dev = &md->dev;
bus->md = md;
+ pm_runtime_enable(&bus->md->dev);
device_register_err:
return ret;
}
@@ -166,6 +167,7 @@ device_register_err:
*/
int sdw_master_device_del(struct sdw_bus *bus)
{
+ pm_runtime_disable(&bus->md->dev);
device_unregister(bus->dev);
return 0;
diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c
index 4ae62b452b8c..55a9c51c84c1 100644
--- a/drivers/soundwire/mipi_disco.c
+++ b/drivers/soundwire/mipi_disco.c
@@ -289,7 +289,7 @@ int sdw_slave_read_prop(struct sdw_slave *slave)
struct sdw_slave_prop *prop = &slave->prop;
struct device *dev = &slave->dev;
struct fwnode_handle *port;
- int num_of_ports, nval, i, dp0 = 0;
+ int nval;
device_property_read_u32(dev, "mipi-sdw-sw-interface-revision",
&prop->mipi_revision);
@@ -352,7 +352,6 @@ int sdw_slave_read_prop(struct sdw_slave *slave)
return -ENOMEM;
sdw_slave_read_dp0(slave, port, prop->dp0_prop);
- dp0 = 1;
}
/*
@@ -383,21 +382,6 @@ int sdw_slave_read_prop(struct sdw_slave *slave)
sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval,
prop->sink_ports, "sink");
- /* some ports are bidirectional so check total ports by ORing */
- nval = prop->source_ports | prop->sink_ports;
- num_of_ports = hweight32(nval) + dp0; /* add DP0 */
-
- /* Allocate port_ready based on num_of_ports */
- slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports,
- sizeof(*slave->port_ready),
- GFP_KERNEL);
- if (!slave->port_ready)
- return -ENOMEM;
-
- /* Initialize completion */
- for (i = 0; i < num_of_ports; i++)
- init_completion(&slave->port_ready[i]);
-
return 0;
}
EXPORT_SYMBOL(sdw_slave_read_prop);
diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c
index 915c2cf0c274..fbca4ebf63e9 100644
--- a/drivers/soundwire/qcom.c
+++ b/drivers/soundwire/qcom.c
@@ -34,6 +34,7 @@
#define SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED BIT(10)
#define SWRM_INTERRUPT_MASK_ADDR 0x204
#define SWRM_INTERRUPT_CLEAR 0x208
+#define SWRM_INTERRUPT_CPU_EN 0x210
#define SWRM_CMD_FIFO_WR_CMD 0x300
#define SWRM_CMD_FIFO_RD_CMD 0x304
#define SWRM_CMD_FIFO_CMD 0x308
@@ -43,19 +44,17 @@
#define SWRM_CMD_FIFO_RD_FIFO_ADDR 0x318
#define SWRM_ENUMERATOR_CFG_ADDR 0x500
#define SWRM_MCP_FRAME_CTRL_BANK_ADDR(m) (0x101C + 0x40 * (m))
-#define SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT 3
#define SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK GENMASK(2, 0)
#define SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK GENMASK(7, 3)
-#define SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT 0
#define SWRM_MCP_CFG_ADDR 0x1048
#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK GENMASK(21, 17)
-#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_SHFT 0x11
#define SWRM_DEF_CMD_NO_PINGS 0x1f
#define SWRM_MCP_STATUS 0x104C
#define SWRM_MCP_STATUS_BANK_NUM_MASK BIT(0)
#define SWRM_MCP_SLV_STATUS 0x1090
#define SWRM_MCP_SLV_STATUS_MASK GENMASK(1, 0)
#define SWRM_DP_PORT_CTRL_BANK(n, m) (0x1124 + 0x100 * (n - 1) + 0x40 * m)
+#define SWRM_DP_BLOCK_CTRL3_BANK(n, m) (0x1138 + 0x100 * (n - 1) + 0x40 * m)
#define SWRM_DP_PORT_CTRL_EN_CHAN_SHFT 0x18
#define SWRM_DP_PORT_CTRL_OFFSET2_SHFT 0x10
#define SWRM_DP_PORT_CTRL_OFFSET1_SHFT 0x08
@@ -67,11 +66,6 @@
#define SWRM_REG_VAL_PACK(data, dev, id, reg) \
((reg) | ((id) << 16) | ((dev) << 20) | ((data) << 24))
-#define SWRM_MAX_ROW_VAL 0 /* Rows = 48 */
-#define SWRM_DEFAULT_ROWS 48
-#define SWRM_MIN_COL_VAL 0 /* Cols = 2 */
-#define SWRM_DEFAULT_COL 16
-#define SWRM_MAX_COL_VAL 7
#define SWRM_SPECIAL_CMD_ID 0xF
#define MAX_FREQ_NUM 1
#define TIMEOUT_MS (2 * HZ)
@@ -84,12 +78,14 @@ struct qcom_swrm_port_config {
u8 si;
u8 off1;
u8 off2;
+ u8 bp_mode;
};
struct qcom_swrm_ctrl {
struct sdw_bus bus;
struct device *dev;
struct regmap *regmap;
+ void __iomem *mmio;
struct completion *comp;
struct work_struct slave_work;
/* read/write lock */
@@ -103,6 +99,8 @@ struct qcom_swrm_ctrl {
unsigned int version;
int num_din_ports;
int num_dout_ports;
+ int cols_index;
+ int rows_index;
unsigned long dout_port_mask;
unsigned long din_port_mask;
struct qcom_swrm_port_config pconfig[QCOM_SDW_MAX_PORTS];
@@ -112,9 +110,24 @@ struct qcom_swrm_ctrl {
int (*reg_write)(struct qcom_swrm_ctrl *ctrl, int reg, int val);
};
+struct qcom_swrm_data {
+ u32 default_cols;
+ u32 default_rows;
+};
+
+static struct qcom_swrm_data swrm_v1_3_data = {
+ .default_rows = 48,
+ .default_cols = 16,
+};
+
+static struct qcom_swrm_data swrm_v1_5_data = {
+ .default_rows = 50,
+ .default_cols = 16,
+};
+
#define to_qcom_sdw(b) container_of(b, struct qcom_swrm_ctrl, bus)
-static int qcom_swrm_abh_reg_read(struct qcom_swrm_ctrl *ctrl, int reg,
+static int qcom_swrm_ahb_reg_read(struct qcom_swrm_ctrl *ctrl, int reg,
u32 *val)
{
struct regmap *wcd_regmap = ctrl->regmap;
@@ -154,6 +167,20 @@ static int qcom_swrm_ahb_reg_write(struct qcom_swrm_ctrl *ctrl,
return SDW_CMD_OK;
}
+static int qcom_swrm_cpu_reg_read(struct qcom_swrm_ctrl *ctrl, int reg,
+ u32 *val)
+{
+ *val = readl(ctrl->mmio + reg);
+ return SDW_CMD_OK;
+}
+
+static int qcom_swrm_cpu_reg_write(struct qcom_swrm_ctrl *ctrl, int reg,
+ int val)
+{
+ writel(val, ctrl->mmio + reg);
+ return SDW_CMD_OK;
+}
+
static int qcom_swrm_cmd_fifo_wr_cmd(struct qcom_swrm_ctrl *ctrl, u8 cmd_data,
u8 dev_addr, u16 reg_addr)
{
@@ -284,8 +311,8 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl)
u32 val;
/* Clear Rows and Cols */
- val = (SWRM_MAX_ROW_VAL << SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT |
- SWRM_MIN_COL_VAL << SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT);
+ val = FIELD_PREP(SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK, ctrl->rows_index);
+ val |= FIELD_PREP(SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK, ctrl->cols_index);
ctrl->reg_write(ctrl, SWRM_MCP_FRAME_CTRL_BANK_ADDR(0), val);
@@ -298,9 +325,7 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl)
/* Configure No pings */
ctrl->reg_read(ctrl, SWRM_MCP_CFG_ADDR, &val);
- val &= ~SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK;
- val |= (SWRM_DEF_CMD_NO_PINGS <<
- SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_SHFT);
+ u32p_replace_bits(&val, SWRM_DEF_CMD_NO_PINGS, SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK);
ctrl->reg_write(ctrl, SWRM_MCP_CFG_ADDR, val);
/* Configure number of retries of a read/write cmd */
@@ -310,6 +335,12 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl)
ctrl->reg_write(ctrl, SWRM_COMP_CFG_ADDR,
SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_MSK |
SWRM_COMP_CFG_ENABLE_MSK);
+
+ /* enable CPU IRQs */
+ if (ctrl->mmio) {
+ ctrl->reg_write(ctrl, SWRM_INTERRUPT_CPU_EN,
+ SWRM_INTERRUPT_STATUS_RMSK);
+ }
return 0;
}
@@ -355,11 +386,8 @@ static int qcom_swrm_pre_bank_switch(struct sdw_bus *bus)
ctrl->reg_read(ctrl, reg, &val);
- val &= ~SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK;
- val &= ~SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK;
-
- val |= (SWRM_MAX_ROW_VAL << SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT |
- SWRM_MAX_COL_VAL << SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT);
+ u32p_replace_bits(&val, ctrl->cols_index, SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK);
+ u32p_replace_bits(&val, ctrl->rows_index, SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK);
return ctrl->reg_write(ctrl, reg, val);
}
@@ -378,14 +406,22 @@ static int qcom_swrm_transport_params(struct sdw_bus *bus,
{
struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
u32 value;
+ int reg = SWRM_DP_PORT_CTRL_BANK((params->port_num), bank);
+ int ret;
value = params->offset1 << SWRM_DP_PORT_CTRL_OFFSET1_SHFT;
value |= params->offset2 << SWRM_DP_PORT_CTRL_OFFSET2_SHFT;
value |= params->sample_interval - 1;
- return ctrl->reg_write(ctrl,
- SWRM_DP_PORT_CTRL_BANK((params->port_num), bank),
- value);
+ ret = ctrl->reg_write(ctrl, reg, value);
+
+ if (!ret && params->blk_pkg_mode) {
+ reg = SWRM_DP_BLOCK_CTRL3_BANK(params->port_num, bank);
+
+ ret = ctrl->reg_write(ctrl, reg, 1);
+ }
+
+ return ret;
}
static int qcom_swrm_port_enable(struct sdw_bus *bus,
@@ -433,6 +469,7 @@ static int qcom_swrm_compute_params(struct sdw_bus *bus)
p_rt->transport_params.sample_interval = pcfg->si + 1;
p_rt->transport_params.offset1 = pcfg->off1;
p_rt->transport_params.offset2 = pcfg->off2;
+ p_rt->transport_params.blk_pkg_mode = pcfg->bp_mode;
}
list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
@@ -443,6 +480,7 @@ static int qcom_swrm_compute_params(struct sdw_bus *bus)
pcfg->si + 1;
p_rt->transport_params.offset1 = pcfg->off1;
p_rt->transport_params.offset2 = pcfg->off2;
+ p_rt->transport_params.blk_pkg_mode = pcfg->bp_mode;
i++;
}
}
@@ -689,12 +727,13 @@ static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl)
u8 off1[QCOM_SDW_MAX_PORTS];
u8 off2[QCOM_SDW_MAX_PORTS];
u8 si[QCOM_SDW_MAX_PORTS];
+ u8 bp_mode[QCOM_SDW_MAX_PORTS] = { 0, };
int i, ret, nports, val;
ctrl->reg_read(ctrl, SWRM_COMP_PARAMS, &val);
- ctrl->num_dout_ports = val & SWRM_COMP_PARAMS_DOUT_PORTS_MASK;
- ctrl->num_din_ports = (val & SWRM_COMP_PARAMS_DIN_PORTS_MASK) >> 5;
+ ctrl->num_dout_ports = FIELD_GET(SWRM_COMP_PARAMS_DOUT_PORTS_MASK, val);
+ ctrl->num_din_ports = FIELD_GET(SWRM_COMP_PARAMS_DIN_PORTS_MASK, val);
ret = of_property_read_u32(np, "qcom,din-ports", &val);
if (ret)
@@ -731,10 +770,13 @@ static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl)
if (ret)
return ret;
+ ret = of_property_read_u8_array(np, "qcom,ports-block-pack-mode",
+ bp_mode, nports);
for (i = 0; i < nports; i++) {
ctrl->pconfig[i].si = si[i];
ctrl->pconfig[i].off1 = off1[i];
ctrl->pconfig[i].off2 = off2[i];
+ ctrl->pconfig[i].bp_mode = bp_mode[i];
}
return 0;
@@ -746,6 +788,7 @@ static int qcom_swrm_probe(struct platform_device *pdev)
struct sdw_master_prop *prop;
struct sdw_bus_params *params;
struct qcom_swrm_ctrl *ctrl;
+ const struct qcom_swrm_data *data;
int ret;
u32 val;
@@ -753,15 +796,25 @@ static int qcom_swrm_probe(struct platform_device *pdev)
if (!ctrl)
return -ENOMEM;
+ data = of_device_get_match_data(dev);
+ ctrl->rows_index = sdw_find_row_index(data->default_rows);
+ ctrl->cols_index = sdw_find_col_index(data->default_cols);
+#if IS_ENABLED(CONFIG_SLIMBUS)
if (dev->parent->bus == &slimbus_bus) {
- ctrl->reg_read = qcom_swrm_abh_reg_read;
+#else
+ if (false) {
+#endif
+ ctrl->reg_read = qcom_swrm_ahb_reg_read;
ctrl->reg_write = qcom_swrm_ahb_reg_write;
ctrl->regmap = dev_get_regmap(dev->parent, NULL);
if (!ctrl->regmap)
return -EINVAL;
} else {
- /* Only WCD based SoundWire controller is supported */
- return -ENOTSUPP;
+ ctrl->reg_read = qcom_swrm_cpu_reg_read;
+ ctrl->reg_write = qcom_swrm_cpu_reg_write;
+ ctrl->mmio = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctrl->mmio))
+ return PTR_ERR(ctrl->mmio);
}
ctrl->irq = of_irq_get(dev->of_node, 0);
@@ -795,8 +848,8 @@ static int qcom_swrm_probe(struct platform_device *pdev)
params = &ctrl->bus.params;
params->max_dr_freq = DEFAULT_CLK_FREQ;
params->curr_dr_freq = DEFAULT_CLK_FREQ;
- params->col = SWRM_DEFAULT_COL;
- params->row = SWRM_DEFAULT_ROWS;
+ params->col = data->default_cols;
+ params->row = data->default_rows;
ctrl->reg_read(ctrl, SWRM_MCP_STATUS, &val);
params->curr_bank = val & SWRM_MCP_STATUS_BANK_NUM_MASK;
params->next_bank = !params->curr_bank;
@@ -806,8 +859,8 @@ static int qcom_swrm_probe(struct platform_device *pdev)
prop->num_clk_gears = 0;
prop->num_clk_freq = MAX_FREQ_NUM;
prop->clk_freq = &qcom_swrm_freq_tbl[0];
- prop->default_col = SWRM_DEFAULT_COL;
- prop->default_row = SWRM_DEFAULT_ROWS;
+ prop->default_col = data->default_cols;
+ prop->default_row = data->default_rows;
ctrl->reg_read(ctrl, SWRM_COMP_HW_VERSION, &ctrl->version);
@@ -858,7 +911,8 @@ static int qcom_swrm_remove(struct platform_device *pdev)
}
static const struct of_device_id qcom_swrm_of_match[] = {
- { .compatible = "qcom,soundwire-v1.3.0", },
+ { .compatible = "qcom,soundwire-v1.3.0", .data = &swrm_v1_3_data },
+ { .compatible = "qcom,soundwire-v1.5.1", .data = &swrm_v1_5_data },
{/* sentinel */},
};
diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c
index 0839445ee07b..a08f4081c1c4 100644
--- a/drivers/soundwire/slave.c
+++ b/drivers/soundwire/slave.c
@@ -6,6 +6,7 @@
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
+#include "sysfs_local.h"
static void sdw_slave_release(struct device *dev)
{
@@ -20,11 +21,12 @@ struct device_type sdw_slave_type = {
.uevent = sdw_slave_uevent,
};
-static int sdw_slave_add(struct sdw_bus *bus,
- struct sdw_slave_id *id, struct fwnode_handle *fwnode)
+int sdw_slave_add(struct sdw_bus *bus,
+ struct sdw_slave_id *id, struct fwnode_handle *fwnode)
{
struct sdw_slave *slave;
int ret;
+ int i;
slave = kzalloc(sizeof(*slave), GFP_KERNEL);
if (!slave)
@@ -50,6 +52,7 @@ static int sdw_slave_add(struct sdw_bus *bus,
slave->dev.bus = &sdw_bus_type;
slave->dev.of_node = of_node_get(to_of_node(fwnode));
slave->dev.type = &sdw_slave_type;
+ slave->dev.groups = sdw_slave_status_attr_groups;
slave->bus = bus;
slave->status = SDW_SLAVE_UNATTACHED;
init_completion(&slave->enumeration_complete);
@@ -57,6 +60,10 @@ static int sdw_slave_add(struct sdw_bus *bus,
slave->dev_num = 0;
init_completion(&slave->probe_complete);
slave->probed = false;
+ slave->first_interrupt_done = false;
+
+ for (i = 0; i < SDW_MAX_PORTS; i++)
+ init_completion(&slave->port_ready[i]);
mutex_lock(&bus->bus_lock);
list_add_tail(&slave->node, &bus->slaves);
@@ -102,7 +109,7 @@ static bool find_slave(struct sdw_bus *bus,
}
/* Extract link id from ADR, Bit 51 to 48 (included) */
- link_id = (addr >> 48) & GENMASK(3, 0);
+ link_id = SDW_DISCO_LINK_ID(addr);
/* Check for link_id match */
if (link_id != bus->link_id)
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c
index 6e36deb505b1..1099b5d1262b 100644
--- a/drivers/soundwire/stream.c
+++ b/drivers/soundwire/stream.c
@@ -25,8 +25,10 @@
int sdw_rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147,
96, 100, 120, 128, 150, 160, 250, 0,
192, 200, 240, 256, 72, 144, 90, 180};
+EXPORT_SYMBOL(sdw_rows);
int sdw_cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16};
+EXPORT_SYMBOL(sdw_cols);
int sdw_find_col_index(int col)
{
@@ -100,9 +102,7 @@ static int _sdw_program_slave_port_params(struct sdw_bus *bus,
return ret;
/* Program DPN_SampleCtrl2 register */
- wbuf = (t_params->sample_interval - 1);
- wbuf &= SDW_DPN_SAMPLECTRL_HIGH;
- wbuf >>= SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH);
+ wbuf = FIELD_GET(SDW_DPN_SAMPLECTRL_HIGH, t_params->sample_interval - 1);
ret = sdw_write(slave, addr3, wbuf);
if (ret < 0) {
@@ -111,9 +111,8 @@ static int _sdw_program_slave_port_params(struct sdw_bus *bus,
}
/* Program DPN_HCtrl register */
- wbuf = t_params->hstart;
- wbuf <<= SDW_REG_SHIFT(SDW_DPN_HCTRL_HSTART);
- wbuf |= t_params->hstop;
+ wbuf = FIELD_PREP(SDW_DPN_HCTRL_HSTART, t_params->hstart);
+ wbuf |= FIELD_PREP(SDW_DPN_HCTRL_HSTOP, t_params->hstop);
ret = sdw_write(slave, addr4, wbuf);
if (ret < 0)
@@ -157,8 +156,8 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus,
}
/* Program DPN_PortCtrl register */
- wbuf = p_params->data_mode << SDW_REG_SHIFT(SDW_DPN_PORTCTRL_DATAMODE);
- wbuf |= p_params->flow_mode;
+ wbuf = FIELD_PREP(SDW_DPN_PORTCTRL_DATAMODE, p_params->data_mode);
+ wbuf |= FIELD_PREP(SDW_DPN_PORTCTRL_FLOWMODE, p_params->flow_mode);
ret = sdw_update(s_rt->slave, addr1, 0xF, wbuf);
if (ret < 0) {
@@ -444,7 +443,8 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
prep_ch.bank = bus->params.next_bank;
- if (dpn_prop->imp_def_interrupts || !dpn_prop->simple_ch_prep_sm)
+ if (dpn_prop->imp_def_interrupts || !dpn_prop->simple_ch_prep_sm ||
+ bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL)
intr = true;
/*
@@ -689,9 +689,9 @@ static int sdw_bank_switch(struct sdw_bus *bus, int m_rt_count)
/*
* Set the multi_link flag only when both the hardware supports
- * and there is a stream handled by multiple masters
+ * and hardware-based sync is required
*/
- multi_link = bus->multi_link && (m_rt_count > 1);
+ multi_link = bus->multi_link && (m_rt_count >= bus->hw_sync_min_links);
if (multi_link)
ret = sdw_transfer_defer(bus, wr_msg, &bus->defer_msg);
@@ -761,13 +761,16 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
const struct sdw_master_ops *ops;
struct sdw_bus *bus;
bool multi_link = false;
+ int m_rt_count;
int ret = 0;
+ m_rt_count = stream->m_rt_count;
+
list_for_each_entry(m_rt, &stream->master_list, stream_node) {
bus = m_rt->bus;
ops = bus->ops;
- if (bus->multi_link) {
+ if (bus->multi_link && m_rt_count >= bus->hw_sync_min_links) {
multi_link = true;
mutex_lock(&bus->msg_lock);
}
@@ -788,7 +791,7 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
* synchronized across all Masters and happens later as a
* part of post_bank_switch ops.
*/
- ret = sdw_bank_switch(bus, stream->m_rt_count);
+ ret = sdw_bank_switch(bus, m_rt_count);
if (ret < 0) {
dev_err(bus->dev, "Bank switch failed: %d\n", ret);
goto error;
@@ -814,7 +817,7 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
ret);
goto error;
}
- } else if (bus->multi_link && stream->m_rt_count > 1) {
+ } else if (multi_link) {
dev_err(bus->dev,
"Post bank switch ops not implemented\n");
goto error;
@@ -832,7 +835,7 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
goto error;
}
- if (bus->multi_link)
+ if (multi_link)
mutex_unlock(&bus->msg_lock);
}
@@ -1784,6 +1787,16 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream)
bus->params.bandwidth -= m_rt->stream->params.rate *
m_rt->ch_count * m_rt->stream->params.bps;
+ /* Compute params */
+ if (bus->compute_params) {
+ ret = bus->compute_params(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Compute params failed: %d",
+ ret);
+ return ret;
+ }
+ }
+
/* Program params */
ret = sdw_program_params(bus, false);
if (ret < 0) {
@@ -1913,7 +1926,7 @@ void sdw_shutdown_stream(void *sdw_substream)
sdw_stream = snd_soc_dai_get_sdw_stream(dai, substream->stream);
- if (!sdw_stream) {
+ if (IS_ERR(sdw_stream)) {
dev_err(rtd->dev, "no stream found for DAI %s", dai->name);
return;
}
diff --git a/drivers/soundwire/sysfs_local.h b/drivers/soundwire/sysfs_local.h
index ff60adee3c41..7268bc24c538 100644
--- a/drivers/soundwire/sysfs_local.h
+++ b/drivers/soundwire/sysfs_local.h
@@ -8,6 +8,10 @@
* SDW sysfs APIs -
*/
+/* basic attributes to report status of Slave (attachment, dev_num) */
+extern const struct attribute_group *sdw_slave_status_attr_groups[];
+
+/* additional device-managed properties reported after driver probe */
int sdw_slave_sysfs_init(struct sdw_slave *slave);
int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave);
diff --git a/drivers/soundwire/sysfs_slave.c b/drivers/soundwire/sysfs_slave.c
index f510071b0add..b48b6617a396 100644
--- a/drivers/soundwire/sysfs_slave.c
+++ b/drivers/soundwire/sysfs_slave.c
@@ -16,9 +16,13 @@
/*
* The sysfs for Slave reflects the MIPI description as given
- * in the MIPI DisCo spec
+ * in the MIPI DisCo spec.
+ * status and device_number come directly from the MIPI SoundWire
+ * 1.x specification.
*
* Base file is device
+ * |---- status
+ * |---- device_number
* |---- modalias
* |---- dev-properties
* |---- mipi_revision
@@ -212,3 +216,55 @@ int sdw_slave_sysfs_init(struct sdw_slave *slave)
return 0;
}
+
+/*
+ * the status is shown in capital letters for UNATTACHED and RESERVED
+ * on purpose, to highligh users to the fact that these status values
+ * are not expected.
+ */
+static const char *const slave_status[] = {
+ [SDW_SLAVE_UNATTACHED] = "UNATTACHED",
+ [SDW_SLAVE_ATTACHED] = "Attached",
+ [SDW_SLAVE_ALERT] = "Alert",
+ [SDW_SLAVE_RESERVED] = "RESERVED",
+};
+
+static ssize_t status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+
+ return sprintf(buf, "%s\n", slave_status[slave->status]);
+}
+static DEVICE_ATTR_RO(status);
+
+static ssize_t device_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+
+ if (slave->status == SDW_SLAVE_UNATTACHED)
+ return sprintf(buf, "%s", "N/A");
+ else
+ return sprintf(buf, "%d", slave->dev_num);
+}
+static DEVICE_ATTR_RO(device_number);
+
+static struct attribute *slave_status_attrs[] = {
+ &dev_attr_status.attr,
+ &dev_attr_device_number.attr,
+ NULL,
+};
+
+/*
+ * we don't use ATTRIBUTES_GROUP here since the group is used in a
+ * separate file and can't be handled as a static.
+ */
+static const struct attribute_group sdw_slave_status_attr_group = {
+ .attrs = slave_status_attrs,
+};
+
+const struct attribute_group *sdw_slave_status_attr_groups[] = {
+ &sdw_slave_status_attr_group,
+ NULL
+};