summaryrefslogtreecommitdiffstats
path: root/drivers/soundwire
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soundwire')
-rw-r--r--drivers/soundwire/Kconfig49
-rw-r--r--drivers/soundwire/Makefile28
-rw-r--r--drivers/soundwire/bus.c1967
-rw-r--r--drivers/soundwire/bus.h217
-rw-r--r--drivers/soundwire/bus_type.c246
-rw-r--r--drivers/soundwire/cadence_master.c1865
-rw-r--r--drivers/soundwire/cadence_master.h199
-rw-r--r--drivers/soundwire/debugfs.c151
-rw-r--r--drivers/soundwire/dmi-quirks.c171
-rw-r--r--drivers/soundwire/generic_bandwidth_allocation.c428
-rw-r--r--drivers/soundwire/intel.c2045
-rw-r--r--drivers/soundwire/intel.h61
-rw-r--r--drivers/soundwire/intel_init.c414
-rw-r--r--drivers/soundwire/master.c188
-rw-r--r--drivers/soundwire/mipi_disco.c387
-rw-r--r--drivers/soundwire/qcom.c1616
-rw-r--r--drivers/soundwire/slave.c266
-rw-r--r--drivers/soundwire/stream.c2106
-rw-r--r--drivers/soundwire/sysfs_local.h18
-rw-r--r--drivers/soundwire/sysfs_slave.c270
-rw-r--r--drivers/soundwire/sysfs_slave_dpn.c301
21 files changed, 12993 insertions, 0 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig
new file mode 100644
index 000000000..2b7795233
--- /dev/null
+++ b/drivers/soundwire/Kconfig
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# SoundWire subsystem configuration
+#
+
+menuconfig SOUNDWIRE
+ tristate "SoundWire support"
+ depends on ACPI || OF
+ help
+ SoundWire is a 2-Pin interface with data and clock line ratified
+ by the MIPI Alliance. SoundWire is used for transporting data
+ typically related to audio functions. SoundWire interface is
+ optimized to integrate audio devices in mobile or mobile inspired
+ systems. Say Y to enable this subsystem, N if you do not have such
+ a device
+
+if SOUNDWIRE
+
+comment "SoundWire Devices"
+
+config SOUNDWIRE_CADENCE
+ tristate
+
+config SOUNDWIRE_INTEL
+ tristate "Intel SoundWire Master driver"
+ select SOUNDWIRE_CADENCE
+ select SOUNDWIRE_GENERIC_ALLOCATION
+ select AUXILIARY_BUS
+ depends on ACPI && SND_SOC
+ help
+ SoundWire Intel Master driver.
+ If you have an Intel platform which has a SoundWire Master then
+ enable this config option to get the SoundWire support for that
+ device.
+
+config SOUNDWIRE_QCOM
+ tristate "Qualcomm SoundWire Master driver"
+ 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
new file mode 100644
index 000000000..986776787
--- /dev/null
+++ b/drivers/soundwire/Makefile
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for soundwire core
+#
+
+#Bus Objs
+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
+
+#Cadence Objs
+soundwire-cadence-y := cadence_master.o
+obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o
+
+#Intel driver
+soundwire-intel-y := intel.o intel_init.o dmi-quirks.o
+obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o
+
+#Qualcomm driver
+soundwire-qcom-y := qcom.o
+obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c
new file mode 100644
index 000000000..f7b3e6a69
--- /dev/null
+++ b/drivers/soundwire/bus.c
@@ -0,0 +1,1967 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+#include "sysfs_local.h"
+
+static DEFINE_IDA(sdw_bus_ida);
+static DEFINE_IDA(sdw_peripheral_ida);
+
+static int sdw_get_id(struct sdw_bus *bus)
+{
+ int rc = ida_alloc(&sdw_bus_ida, GFP_KERNEL);
+
+ if (rc < 0)
+ return rc;
+
+ bus->id = rc;
+ return 0;
+}
+
+/**
+ * sdw_bus_master_add() - add a bus Master instance
+ * @bus: bus instance
+ * @parent: parent device
+ * @fwnode: firmware node handle
+ *
+ * Initializes the bus instance, read properties and create child
+ * devices.
+ */
+int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent,
+ struct fwnode_handle *fwnode)
+{
+ struct sdw_master_prop *prop = NULL;
+ int ret;
+
+ if (!parent) {
+ pr_err("SoundWire parent device is not set\n");
+ return -ENODEV;
+ }
+
+ ret = sdw_get_id(bus);
+ if (ret < 0) {
+ dev_err(parent, "Failed to get bus id\n");
+ return ret;
+ }
+
+ ret = sdw_master_device_add(bus, parent, fwnode);
+ if (ret < 0) {
+ dev_err(parent, "Failed to add master device at link %d\n",
+ bus->link_id);
+ return ret;
+ }
+
+ if (!bus->ops) {
+ dev_err(bus->dev, "SoundWire Bus ops are not set\n");
+ 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);
+ INIT_LIST_HEAD(&bus->m_rt_list);
+
+ /*
+ * Initialize multi_link flag
+ */
+ bus->multi_link = false;
+ if (bus->ops->read_prop) {
+ ret = bus->ops->read_prop(bus);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Bus read properties failed:%d\n", ret);
+ return ret;
+ }
+ }
+
+ sdw_bus_debugfs_init(bus);
+
+ /*
+ * Device numbers in SoundWire are 0 through 15. Enumeration device
+ * number (0), Broadcast device number (15), Group numbers (12 and
+ * 13) and Master device number (14) are not used for assignment so
+ * mask these and other higher bits.
+ */
+
+ /* Set higher order bits */
+ *bus->assigned = ~GENMASK(SDW_BROADCAST_DEV_NUM, SDW_ENUM_DEV_NUM);
+
+ /* Set enumuration device number and broadcast device number */
+ set_bit(SDW_ENUM_DEV_NUM, bus->assigned);
+ set_bit(SDW_BROADCAST_DEV_NUM, bus->assigned);
+
+ /* Set group device numbers and master device number */
+ set_bit(SDW_GROUP12_DEV_NUM, bus->assigned);
+ set_bit(SDW_GROUP13_DEV_NUM, bus->assigned);
+ set_bit(SDW_MASTER_DEV_NUM, bus->assigned);
+
+ /*
+ * SDW is an enumerable bus, but devices can be powered off. So,
+ * they won't be able to report as present.
+ *
+ * Create Slave devices based on Slaves described in
+ * the respective firmware (ACPI/DT)
+ */
+ if (IS_ENABLED(CONFIG_ACPI) && ACPI_HANDLE(bus->dev))
+ ret = sdw_acpi_find_slaves(bus);
+ else if (IS_ENABLED(CONFIG_OF) && bus->dev->of_node)
+ ret = sdw_of_find_slaves(bus);
+ else
+ ret = -ENOTSUPP; /* No ACPI/DT so error out */
+
+ if (ret < 0) {
+ dev_err(bus->dev, "Finding slaves failed:%d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Initialize clock values based on Master properties. The max
+ * frequency is read from max_clk_freq property. Current assumption
+ * is that the bus will start at highest clock frequency when
+ * powered on.
+ *
+ * Default active bank will be 0 as out of reset the Slaves have
+ * to start with bank 0 (Table 40 of Spec)
+ */
+ prop = &bus->prop;
+ bus->params.max_dr_freq = prop->max_clk_freq * SDW_DOUBLE_RATE_FACTOR;
+ bus->params.curr_dr_freq = bus->params.max_dr_freq;
+ bus->params.curr_bank = SDW_BANK0;
+ bus->params.next_bank = SDW_BANK1;
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_bus_master_add);
+
+static int sdw_delete_slave(struct device *dev, void *data)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct sdw_bus *bus = slave->bus;
+
+ pm_runtime_disable(dev);
+
+ sdw_slave_debugfs_exit(slave);
+
+ mutex_lock(&bus->bus_lock);
+
+ if (slave->dev_num) { /* clear dev_num if assigned */
+ clear_bit(slave->dev_num, bus->assigned);
+ if (bus->dev_num_ida_min)
+ ida_free(&sdw_peripheral_ida, slave->dev_num);
+ }
+ list_del_init(&slave->node);
+ mutex_unlock(&bus->bus_lock);
+
+ device_unregister(dev);
+ return 0;
+}
+
+/**
+ * sdw_bus_master_delete() - delete the bus master instance
+ * @bus: bus to be deleted
+ *
+ * Remove the instance, delete the child devices.
+ */
+void sdw_bus_master_delete(struct sdw_bus *bus)
+{
+ device_for_each_child(bus->dev, NULL, sdw_delete_slave);
+ sdw_master_device_del(bus);
+
+ sdw_bus_debugfs_exit(bus);
+ ida_free(&sdw_bus_ida, bus->id);
+}
+EXPORT_SYMBOL(sdw_bus_master_delete);
+
+/*
+ * SDW IO Calls
+ */
+
+static inline int find_response_code(enum sdw_command_response resp)
+{
+ switch (resp) {
+ case SDW_CMD_OK:
+ return 0;
+
+ case SDW_CMD_IGNORED:
+ return -ENODATA;
+
+ case SDW_CMD_TIMEOUT:
+ return -ETIMEDOUT;
+
+ default:
+ return -EIO;
+ }
+}
+
+static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg)
+{
+ int retry = bus->prop.err_threshold;
+ enum sdw_command_response resp;
+ int ret = 0, i;
+
+ for (i = 0; i <= retry; i++) {
+ resp = bus->ops->xfer_msg(bus, msg);
+ ret = find_response_code(resp);
+
+ /* if cmd is ok or ignored return */
+ if (ret == 0 || ret == -ENODATA)
+ return ret;
+ }
+
+ return ret;
+}
+
+static inline int do_transfer_defer(struct sdw_bus *bus,
+ struct sdw_msg *msg,
+ struct sdw_defer *defer)
+{
+ int retry = bus->prop.err_threshold;
+ enum sdw_command_response resp;
+ int ret = 0, i;
+
+ defer->msg = msg;
+ defer->length = msg->len;
+ init_completion(&defer->complete);
+
+ for (i = 0; i <= retry; i++) {
+ resp = bus->ops->xfer_msg_defer(bus, msg, defer);
+ ret = find_response_code(resp);
+ /* if cmd is ok or ignored return */
+ if (ret == 0 || ret == -ENODATA)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int sdw_reset_page(struct sdw_bus *bus, u16 dev_num)
+{
+ int retry = bus->prop.err_threshold;
+ enum sdw_command_response resp;
+ int ret = 0, i;
+
+ for (i = 0; i <= retry; i++) {
+ resp = bus->ops->reset_page_addr(bus, dev_num);
+ ret = find_response_code(resp);
+ /* if cmd is ok or ignored return */
+ if (ret == 0 || ret == -ENODATA)
+ return ret;
+ }
+
+ 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 %s addr %x count %d\n",
+ msg->dev_num, ret,
+ (msg->flags & SDW_MSG_FLAG_WRITE) ? "write" : "read",
+ msg->addr, msg->len);
+
+ 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
+ * @msg: SDW message to be xfered
+ */
+int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg)
+{
+ int ret;
+
+ mutex_lock(&bus->msg_lock);
+
+ ret = sdw_transfer_unlocked(bus, msg);
+
+ mutex_unlock(&bus->msg_lock);
+
+ return ret;
+}
+
+/**
+ * sdw_show_ping_status() - Direct report of PING status, to be used by Peripheral drivers
+ * @bus: SDW bus
+ * @sync_delay: Delay before reading status
+ */
+void sdw_show_ping_status(struct sdw_bus *bus, bool sync_delay)
+{
+ u32 status;
+
+ if (!bus->ops->read_ping_status)
+ return;
+
+ /*
+ * wait for peripheral to sync if desired. 10-15ms should be more than
+ * enough in most cases.
+ */
+ if (sync_delay)
+ usleep_range(10000, 15000);
+
+ mutex_lock(&bus->msg_lock);
+
+ status = bus->ops->read_ping_status(bus);
+
+ mutex_unlock(&bus->msg_lock);
+
+ if (!status)
+ dev_warn(bus->dev, "%s: no peripherals attached\n", __func__);
+ else
+ dev_dbg(bus->dev, "PING status: %#x\n", status);
+}
+EXPORT_SYMBOL(sdw_show_ping_status);
+
+/**
+ * sdw_transfer_defer() - Asynchronously transfer message to a SDW Slave device
+ * @bus: SDW bus
+ * @msg: SDW message to be xfered
+ * @defer: Defer block for signal completion
+ *
+ * Caller needs to hold the msg_lock lock while calling this
+ */
+int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg,
+ struct sdw_defer *defer)
+{
+ int ret;
+
+ if (!bus->ops->xfer_msg_defer)
+ return -ENOTSUPP;
+
+ ret = do_transfer_defer(bus, msg, defer);
+ if (ret != 0 && ret != -ENODATA)
+ dev_err(bus->dev, "Defer trf on Slave %d failed:%d\n",
+ msg->dev_num, ret);
+
+ if (msg->page)
+ sdw_reset_page(bus, msg->dev_num);
+
+ return ret;
+}
+
+int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
+ u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf)
+{
+ memset(msg, 0, sizeof(*msg));
+ msg->addr = addr; /* addr is 16 bit and truncated here */
+ msg->len = count;
+ msg->dev_num = dev_num;
+ msg->flags = flags;
+ msg->buf = buf;
+
+ if (addr < SDW_REG_NO_PAGE) /* no paging area */
+ return 0;
+
+ if (addr >= SDW_REG_MAX) { /* illegal addr */
+ pr_err("SDW: Invalid address %x passed\n", addr);
+ return -EINVAL;
+ }
+
+ if (addr < SDW_REG_OPTIONAL_PAGE) { /* 32k but no page */
+ if (slave && !slave->prop.paging_support)
+ return 0;
+ /* no need for else as that will fall-through to paging */
+ }
+
+ /* paging mandatory */
+ if (dev_num == SDW_ENUM_DEV_NUM || dev_num == SDW_BROADCAST_DEV_NUM) {
+ pr_err("SDW: Invalid device for paging :%d\n", dev_num);
+ return -EINVAL;
+ }
+
+ if (!slave) {
+ pr_err("SDW: No slave for paging addr\n");
+ return -EINVAL;
+ }
+
+ if (!slave->prop.paging_support) {
+ dev_err(&slave->dev,
+ "address %x needs paging but no support\n", addr);
+ return -EINVAL;
+ }
+
+ 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;
+
+ return 0;
+}
+
+/*
+ * Read/Write IO functions.
+ * no_pm versions can only be called by the bus, e.g. while enumerating or
+ * handling suspend-resume sequences.
+ * all clients need to use the pm versions
+ */
+
+static int
+sdw_nread_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
+{
+ struct sdw_msg msg;
+ int ret;
+
+ ret = sdw_fill_msg(&msg, slave, addr, count,
+ slave->dev_num, SDW_MSG_FLAG_READ, val);
+ if (ret < 0)
+ return ret;
+
+ ret = sdw_transfer(slave->bus, &msg);
+ if (slave->is_mockup_device)
+ ret = 0;
+ return ret;
+}
+
+static int
+sdw_nwrite_no_pm(struct sdw_slave *slave, u32 addr, size_t count, const u8 *val)
+{
+ struct sdw_msg msg;
+ int ret;
+
+ ret = sdw_fill_msg(&msg, slave, addr, count,
+ slave->dev_num, SDW_MSG_FLAG_WRITE, (u8 *)val);
+ if (ret < 0)
+ return ret;
+
+ ret = sdw_transfer(slave->bus, &msg);
+ if (slave->is_mockup_device)
+ ret = 0;
+ return ret;
+}
+
+int sdw_write_no_pm(struct sdw_slave *slave, u32 addr, u8 value)
+{
+ return sdw_nwrite_no_pm(slave, addr, 1, &value);
+}
+EXPORT_SYMBOL(sdw_write_no_pm);
+
+static int
+sdw_bread_no_pm(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 < 0)
+ return ret;
+
+ ret = sdw_transfer(bus, &msg);
+ if (ret < 0)
+ return ret;
+
+ return buf;
+}
+
+static int
+sdw_bwrite_no_pm(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 < 0)
+ return ret;
+
+ 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 < 0)
+ 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 < 0)
+ return ret;
+
+ return sdw_transfer_unlocked(bus, &msg);
+}
+EXPORT_SYMBOL(sdw_bwrite_no_pm_unlocked);
+
+int sdw_read_no_pm(struct sdw_slave *slave, u32 addr)
+{
+ u8 buf;
+ int ret;
+
+ ret = sdw_nread_no_pm(slave, addr, 1, &buf);
+ if (ret < 0)
+ return ret;
+ else
+ return buf;
+}
+EXPORT_SYMBOL(sdw_read_no_pm);
+
+int sdw_update_no_pm(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
+{
+ int tmp;
+
+ tmp = sdw_read_no_pm(slave, addr);
+ if (tmp < 0)
+ return tmp;
+
+ tmp = (tmp & ~mask) | val;
+ return sdw_write_no_pm(slave, addr, tmp);
+}
+EXPORT_SYMBOL(sdw_update_no_pm);
+
+/* Read-Modify-Write Slave register */
+int sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
+{
+ int tmp;
+
+ tmp = sdw_read(slave, addr);
+ if (tmp < 0)
+ return tmp;
+
+ tmp = (tmp & ~mask) | val;
+ return sdw_write(slave, addr, tmp);
+}
+EXPORT_SYMBOL(sdw_update);
+
+/**
+ * sdw_nread() - Read "n" contiguous SDW Slave registers
+ * @slave: SDW Slave
+ * @addr: Register address
+ * @count: length
+ * @val: Buffer for values to be read
+ */
+int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
+{
+ int ret;
+
+ ret = pm_runtime_get_sync(&slave->dev);
+ if (ret < 0 && ret != -EACCES) {
+ pm_runtime_put_noidle(&slave->dev);
+ return ret;
+ }
+
+ ret = sdw_nread_no_pm(slave, addr, count, val);
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put(&slave->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL(sdw_nread);
+
+/**
+ * sdw_nwrite() - Write "n" contiguous SDW Slave registers
+ * @slave: SDW Slave
+ * @addr: Register address
+ * @count: length
+ * @val: Buffer for values to be written
+ */
+int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, const u8 *val)
+{
+ int ret;
+
+ ret = pm_runtime_get_sync(&slave->dev);
+ if (ret < 0 && ret != -EACCES) {
+ pm_runtime_put_noidle(&slave->dev);
+ return ret;
+ }
+
+ ret = sdw_nwrite_no_pm(slave, addr, count, val);
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put(&slave->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL(sdw_nwrite);
+
+/**
+ * sdw_read() - Read a SDW Slave register
+ * @slave: SDW Slave
+ * @addr: Register address
+ */
+int sdw_read(struct sdw_slave *slave, u32 addr)
+{
+ u8 buf;
+ int ret;
+
+ ret = sdw_nread(slave, addr, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ return buf;
+}
+EXPORT_SYMBOL(sdw_read);
+
+/**
+ * sdw_write() - Write a SDW Slave register
+ * @slave: SDW Slave
+ * @addr: Register address
+ * @value: Register value
+ */
+int sdw_write(struct sdw_slave *slave, u32 addr, u8 value)
+{
+ return sdw_nwrite(slave, addr, 1, &value);
+}
+EXPORT_SYMBOL(sdw_write);
+
+/*
+ * SDW alert handling
+ */
+
+/* called with bus_lock held */
+static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i)
+{
+ struct sdw_slave *slave;
+
+ list_for_each_entry(slave, &bus->slaves, node) {
+ if (slave->dev_num == i)
+ return slave;
+ }
+
+ return NULL;
+}
+
+int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id)
+{
+ if (slave->id.mfg_id != id.mfg_id ||
+ slave->id.part_id != id.part_id ||
+ slave->id.class_id != id.class_id ||
+ (slave->id.unique_id != SDW_IGNORED_UNIQUE_ID &&
+ slave->id.unique_id != id.unique_id))
+ return -ENODEV;
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_compare_devid);
+
+/* called with bus_lock held */
+static int sdw_get_device_num(struct sdw_slave *slave)
+{
+ int bit;
+
+ if (slave->bus->dev_num_ida_min) {
+ bit = ida_alloc_range(&sdw_peripheral_ida,
+ slave->bus->dev_num_ida_min, SDW_MAX_DEVICES,
+ GFP_KERNEL);
+ if (bit < 0)
+ goto err;
+ } else {
+ bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES);
+ if (bit == SDW_MAX_DEVICES) {
+ bit = -ENODEV;
+ goto err;
+ }
+ }
+
+ /*
+ * Do not update dev_num in Slave data structure here,
+ * Update once program dev_num is successful
+ */
+ set_bit(bit, slave->bus->assigned);
+
+err:
+ return bit;
+}
+
+static int sdw_assign_device_num(struct sdw_slave *slave)
+{
+ struct sdw_bus *bus = slave->bus;
+ int ret, dev_num;
+ bool new_device = false;
+
+ /* check first if device number is assigned, if so reuse that */
+ if (!slave->dev_num) {
+ if (!slave->dev_num_sticky) {
+ mutex_lock(&slave->bus->bus_lock);
+ dev_num = sdw_get_device_num(slave);
+ mutex_unlock(&slave->bus->bus_lock);
+ if (dev_num < 0) {
+ dev_err(bus->dev, "Get dev_num failed: %d\n",
+ dev_num);
+ return dev_num;
+ }
+ slave->dev_num = dev_num;
+ slave->dev_num_sticky = dev_num;
+ new_device = true;
+ } else {
+ slave->dev_num = slave->dev_num_sticky;
+ }
+ }
+
+ if (!new_device)
+ dev_dbg(bus->dev,
+ "Slave already registered, reusing dev_num:%d\n",
+ slave->dev_num);
+
+ /* Clear the slave->dev_num to transfer message on device 0 */
+ dev_num = slave->dev_num;
+ slave->dev_num = 0;
+
+ ret = sdw_write_no_pm(slave, SDW_SCP_DEVNUMBER, dev_num);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program device_num %d failed: %d\n",
+ dev_num, ret);
+ return ret;
+ }
+
+ /* After xfer of msg, restore dev_num */
+ slave->dev_num = slave->dev_num_sticky;
+
+ return 0;
+}
+
+void sdw_extract_slave_id(struct sdw_bus *bus,
+ u64 addr, struct sdw_slave_id *id)
+{
+ dev_dbg(bus->dev, "SDW Slave Addr: %llx\n", addr);
+
+ id->sdw_version = SDW_VERSION(addr);
+ id->unique_id = SDW_UNIQUE_ID(addr);
+ id->mfg_id = SDW_MFG_ID(addr);
+ id->part_id = SDW_PART_ID(addr);
+ id->class_id = SDW_CLASS_ID(addr);
+
+ dev_dbg(bus->dev,
+ "SDW Slave class_id 0x%02x, mfg_id 0x%04x, part_id 0x%04x, unique_id 0x%x, version 0x%x\n",
+ id->class_id, id->mfg_id, id->part_id, id->unique_id, id->sdw_version);
+}
+EXPORT_SYMBOL(sdw_extract_slave_id);
+
+static int sdw_program_device_num(struct sdw_bus *bus, bool *programmed)
+{
+ u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0};
+ struct sdw_slave *slave, *_s;
+ struct sdw_slave_id id;
+ struct sdw_msg msg;
+ bool found;
+ int count = 0, ret;
+ u64 addr;
+
+ *programmed = false;
+
+ /* No Slave, so use raw xfer api */
+ ret = sdw_fill_msg(&msg, NULL, SDW_SCP_DEVID_0,
+ SDW_NUM_DEV_ID_REGISTERS, 0, SDW_MSG_FLAG_READ, buf);
+ if (ret < 0)
+ return ret;
+
+ do {
+ ret = sdw_transfer(bus, &msg);
+ if (ret == -ENODATA) { /* end of device id reads */
+ dev_dbg(bus->dev, "No more devices to enumerate\n");
+ ret = 0;
+ break;
+ }
+ if (ret < 0) {
+ dev_err(bus->dev, "DEVID read fail:%d\n", ret);
+ break;
+ }
+
+ /*
+ * Construct the addr and extract. Cast the higher shift
+ * bits to avoid truncation due to size limit.
+ */
+ addr = buf[5] | (buf[4] << 8) | (buf[3] << 16) |
+ ((u64)buf[2] << 24) | ((u64)buf[1] << 32) |
+ ((u64)buf[0] << 40);
+
+ sdw_extract_slave_id(bus, addr, &id);
+
+ found = false;
+ /* Now compare with entries */
+ list_for_each_entry_safe(slave, _s, &bus->slaves, node) {
+ if (sdw_compare_devid(slave, id) == 0) {
+ found = true;
+
+ /*
+ * To prevent skipping state-machine stages don't
+ * program a device until we've seen it UNATTACH.
+ * Must return here because no other device on #0
+ * can be detected until this one has been
+ * assigned a device ID.
+ */
+ if (slave->status != SDW_SLAVE_UNATTACHED)
+ return 0;
+
+ /*
+ * Assign a new dev_num to this Slave and
+ * not mark it present. It will be marked
+ * present after it reports ATTACHED on new
+ * dev_num
+ */
+ ret = sdw_assign_device_num(slave);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Assign dev_num failed:%d\n",
+ ret);
+ return ret;
+ }
+
+ *programmed = true;
+
+ break;
+ }
+ }
+
+ 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");
+ }
+
+ count++;
+
+ /*
+ * Check till error out or retry (count) exhausts.
+ * Device can drop off and rejoin during enumeration
+ * so count till twice the bound.
+ */
+
+ } while (ret == 0 && count < (SDW_MAX_DEVICES * 2));
+
+ return ret;
+}
+
+static void sdw_modify_slave_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ struct sdw_bus *bus = slave->bus;
+
+ mutex_lock(&bus->bus_lock);
+
+ dev_vdbg(bus->dev,
+ "changing status slave %d status %d new status %d\n",
+ slave->dev_num, slave->status, status);
+
+ if (status == SDW_SLAVE_UNATTACHED) {
+ dev_dbg(&slave->dev,
+ "initializing enumeration and init completion for Slave %d\n",
+ slave->dev_num);
+
+ reinit_completion(&slave->enumeration_complete);
+ reinit_completion(&slave->initialization_complete);
+
+ } else if ((status == SDW_SLAVE_ATTACHED) &&
+ (slave->status == SDW_SLAVE_UNATTACHED)) {
+ dev_dbg(&slave->dev,
+ "signaling enumeration completion for Slave %d\n",
+ slave->dev_num);
+
+ complete_all(&slave->enumeration_complete);
+ }
+ slave->status = status;
+ mutex_unlock(&bus->bus_lock);
+}
+
+static int sdw_slave_clk_stop_callback(struct sdw_slave *slave,
+ enum sdw_clk_stop_mode mode,
+ enum sdw_clk_stop_type type)
+{
+ int ret = 0;
+
+ mutex_lock(&slave->sdw_dev_lock);
+
+ if (slave->probed) {
+ struct device *dev = &slave->dev;
+ struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
+
+ if (drv->ops && drv->ops->clk_stop)
+ ret = drv->ops->clk_stop(slave, mode, type);
+ }
+
+ mutex_unlock(&slave->sdw_dev_lock);
+
+ return ret;
+}
+
+static int sdw_slave_clk_stop_prepare(struct sdw_slave *slave,
+ enum sdw_clk_stop_mode mode,
+ bool prepare)
+{
+ bool wake_en;
+ u32 val = 0;
+ int ret;
+
+ wake_en = slave->prop.wake_capable;
+
+ if (prepare) {
+ val = SDW_SCP_SYSTEMCTRL_CLK_STP_PREP;
+
+ if (mode == SDW_CLK_STOP_MODE1)
+ val |= SDW_SCP_SYSTEMCTRL_CLK_STP_MODE1;
+
+ if (wake_en)
+ val |= SDW_SCP_SYSTEMCTRL_WAKE_UP_EN;
+ } else {
+ ret = sdw_read_no_pm(slave, SDW_SCP_SYSTEMCTRL);
+ if (ret < 0) {
+ if (ret != -ENODATA)
+ dev_err(&slave->dev, "SDW_SCP_SYSTEMCTRL read failed:%d\n", ret);
+ return ret;
+ }
+ val = ret;
+ val &= ~(SDW_SCP_SYSTEMCTRL_CLK_STP_PREP);
+ }
+
+ ret = sdw_write_no_pm(slave, SDW_SCP_SYSTEMCTRL, val);
+
+ if (ret < 0 && ret != -ENODATA)
+ dev_err(&slave->dev, "SDW_SCP_SYSTEMCTRL write failed:%d\n", ret);
+
+ return ret;
+}
+
+static int sdw_bus_wait_for_clk_prep_deprep(struct sdw_bus *bus, u16 dev_num)
+{
+ int retry = bus->clk_stop_timeout;
+ int val;
+
+ do {
+ val = sdw_bread_no_pm(bus, dev_num, SDW_SCP_STAT);
+ if (val < 0) {
+ if (val != -ENODATA)
+ dev_err(bus->dev, "SDW_SCP_STAT bread failed:%d\n", val);
+ return val;
+ }
+ val &= SDW_SCP_STAT_CLK_STP_NF;
+ if (!val) {
+ dev_dbg(bus->dev, "clock stop prep/de-prep done slave:%d\n",
+ dev_num);
+ return 0;
+ }
+
+ usleep_range(1000, 1500);
+ retry--;
+ } while (retry);
+
+ dev_err(bus->dev, "clock stop prep/de-prep failed slave:%d\n",
+ dev_num);
+
+ return -ETIMEDOUT;
+}
+
+/**
+ * sdw_bus_prep_clk_stop: prepare Slave(s) for clock stop
+ *
+ * @bus: SDW bus instance
+ *
+ * Query Slave for clock stop mode and prepare for that mode.
+ */
+int sdw_bus_prep_clk_stop(struct sdw_bus *bus)
+{
+ bool simple_clk_stop = true;
+ struct sdw_slave *slave;
+ bool is_slave = false;
+ int ret = 0;
+
+ /*
+ * In order to save on transition time, prepare
+ * each Slave and then wait for all Slave(s) to be
+ * prepared for clock stop.
+ * If one of the Slave devices has lost sync and
+ * replies with Command Ignored/-ENODATA, we continue
+ * the loop
+ */
+ list_for_each_entry(slave, &bus->slaves, node) {
+ if (!slave->dev_num)
+ continue;
+
+ if (slave->status != SDW_SLAVE_ATTACHED &&
+ slave->status != SDW_SLAVE_ALERT)
+ continue;
+
+ /* Identify if Slave(s) are available on Bus */
+ is_slave = true;
+
+ ret = sdw_slave_clk_stop_callback(slave,
+ SDW_CLK_STOP_MODE0,
+ SDW_CLK_PRE_PREPARE);
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(&slave->dev, "clock stop pre-prepare cb failed:%d\n", ret);
+ return ret;
+ }
+
+ /* Only prepare a Slave device if needed */
+ if (!slave->prop.simple_clk_stop_capable) {
+ simple_clk_stop = false;
+
+ ret = sdw_slave_clk_stop_prepare(slave,
+ SDW_CLK_STOP_MODE0,
+ true);
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(&slave->dev, "clock stop prepare failed:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ /* Skip remaining clock stop preparation if no Slave is attached */
+ if (!is_slave)
+ return 0;
+
+ /*
+ * Don't wait for all Slaves to be ready if they follow the simple
+ * state machine
+ */
+ if (!simple_clk_stop) {
+ ret = sdw_bus_wait_for_clk_prep_deprep(bus,
+ SDW_BROADCAST_DEV_NUM);
+ /*
+ * if there are no Slave devices present and the reply is
+ * Command_Ignored/-ENODATA, we don't need to continue with the
+ * flow and can just return here. The error code is not modified
+ * and its handling left as an exercise for the caller.
+ */
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Inform slaves that prep is done */
+ list_for_each_entry(slave, &bus->slaves, node) {
+ if (!slave->dev_num)
+ continue;
+
+ if (slave->status != SDW_SLAVE_ATTACHED &&
+ slave->status != SDW_SLAVE_ALERT)
+ continue;
+
+ ret = sdw_slave_clk_stop_callback(slave,
+ SDW_CLK_STOP_MODE0,
+ SDW_CLK_POST_PREPARE);
+
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(&slave->dev, "clock stop post-prepare cb failed:%d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_bus_prep_clk_stop);
+
+/**
+ * sdw_bus_clk_stop: stop bus clock
+ *
+ * @bus: SDW bus instance
+ *
+ * After preparing the Slaves for clock stop, stop the clock by broadcasting
+ * write to SCP_CTRL register.
+ */
+int sdw_bus_clk_stop(struct sdw_bus *bus)
+{
+ int ret;
+
+ /*
+ * broadcast clock stop now, attached Slaves will ACK this,
+ * unattached will ignore
+ */
+ ret = sdw_bwrite_no_pm(bus, SDW_BROADCAST_DEV_NUM,
+ SDW_SCP_CTRL, SDW_SCP_CTRL_CLK_STP_NOW);
+ if (ret < 0) {
+ if (ret != -ENODATA)
+ dev_err(bus->dev, "ClockStopNow Broadcast msg failed %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_bus_clk_stop);
+
+/**
+ * sdw_bus_exit_clk_stop: Exit clock stop mode
+ *
+ * @bus: SDW bus instance
+ *
+ * This De-prepares the Slaves by exiting Clock Stop Mode 0. For the Slaves
+ * exiting Clock Stop Mode 1, they will be de-prepared after they enumerate
+ * back.
+ */
+int sdw_bus_exit_clk_stop(struct sdw_bus *bus)
+{
+ bool simple_clk_stop = true;
+ struct sdw_slave *slave;
+ bool is_slave = false;
+ int ret;
+
+ /*
+ * In order to save on transition time, de-prepare
+ * each Slave and then wait for all Slave(s) to be
+ * de-prepared after clock resume.
+ */
+ list_for_each_entry(slave, &bus->slaves, node) {
+ if (!slave->dev_num)
+ continue;
+
+ if (slave->status != SDW_SLAVE_ATTACHED &&
+ slave->status != SDW_SLAVE_ALERT)
+ continue;
+
+ /* Identify if Slave(s) are available on Bus */
+ is_slave = true;
+
+ ret = sdw_slave_clk_stop_callback(slave, SDW_CLK_STOP_MODE0,
+ SDW_CLK_PRE_DEPREPARE);
+ if (ret < 0)
+ dev_warn(&slave->dev, "clock stop pre-deprepare cb failed:%d\n", ret);
+
+ /* Only de-prepare a Slave device if needed */
+ if (!slave->prop.simple_clk_stop_capable) {
+ simple_clk_stop = false;
+
+ ret = sdw_slave_clk_stop_prepare(slave, SDW_CLK_STOP_MODE0,
+ false);
+
+ if (ret < 0)
+ dev_warn(&slave->dev, "clock stop deprepare failed:%d\n", ret);
+ }
+ }
+
+ /* Skip remaining clock stop de-preparation if no Slave is attached */
+ if (!is_slave)
+ return 0;
+
+ /*
+ * Don't wait for all Slaves to be ready if they follow the simple
+ * state machine
+ */
+ if (!simple_clk_stop) {
+ ret = sdw_bus_wait_for_clk_prep_deprep(bus, SDW_BROADCAST_DEV_NUM);
+ if (ret < 0)
+ dev_warn(bus->dev, "clock stop deprepare wait failed:%d\n", ret);
+ }
+
+ list_for_each_entry(slave, &bus->slaves, node) {
+ if (!slave->dev_num)
+ continue;
+
+ if (slave->status != SDW_SLAVE_ATTACHED &&
+ slave->status != SDW_SLAVE_ALERT)
+ continue;
+
+ ret = sdw_slave_clk_stop_callback(slave, SDW_CLK_STOP_MODE0,
+ SDW_CLK_POST_DEPREPARE);
+ if (ret < 0)
+ dev_warn(&slave->dev, "clock stop post-deprepare cb failed:%d\n", ret);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_bus_exit_clk_stop);
+
+int sdw_configure_dpn_intr(struct sdw_slave *slave,
+ int port, bool enable, int mask)
+{
+ u32 addr;
+ 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 */
+ if (enable) {
+ val |= mask;
+ val |= SDW_DPN_INT_PORT_READY;
+ } else {
+ val &= ~(mask);
+ val &= ~SDW_DPN_INT_PORT_READY;
+ }
+
+ ret = sdw_update(slave, addr, (mask | SDW_DPN_INT_PORT_READY), val);
+ if (ret < 0)
+ dev_err(&slave->dev,
+ "SDW_DPN_INTMASK write failed:%d\n", val);
+
+ return ret;
+}
+
+static int sdw_slave_set_frequency(struct sdw_slave *slave)
+{
+ u32 mclk_freq = slave->bus->prop.mclk_freq;
+ u32 curr_freq = slave->bus->params.curr_dr_freq >> 1;
+ unsigned int scale;
+ u8 scale_index;
+ u8 base;
+ int ret;
+
+ /*
+ * frequency base and scale registers are required for SDCA
+ * devices. They may also be used for 1.2+/non-SDCA devices,
+ * but we will need a DisCo property to cover this case
+ */
+ if (!slave->id.class_id)
+ return 0;
+
+ if (!mclk_freq) {
+ dev_err(&slave->dev,
+ "no bus MCLK, cannot set SDW_SCP_BUS_CLOCK_BASE\n");
+ return -EINVAL;
+ }
+
+ /*
+ * map base frequency using Table 89 of SoundWire 1.2 spec.
+ * The order of the tests just follows the specification, this
+ * is not a selection between possible values or a search for
+ * the best value but just a mapping. Only one case per platform
+ * is relevant.
+ * Some BIOS have inconsistent values for mclk_freq but a
+ * correct root so we force the mclk_freq to avoid variations.
+ */
+ if (!(19200000 % mclk_freq)) {
+ mclk_freq = 19200000;
+ base = SDW_SCP_BASE_CLOCK_19200000_HZ;
+ } else if (!(24000000 % mclk_freq)) {
+ mclk_freq = 24000000;
+ base = SDW_SCP_BASE_CLOCK_24000000_HZ;
+ } else if (!(24576000 % mclk_freq)) {
+ mclk_freq = 24576000;
+ base = SDW_SCP_BASE_CLOCK_24576000_HZ;
+ } else if (!(22579200 % mclk_freq)) {
+ mclk_freq = 22579200;
+ base = SDW_SCP_BASE_CLOCK_22579200_HZ;
+ } else if (!(32000000 % mclk_freq)) {
+ mclk_freq = 32000000;
+ base = SDW_SCP_BASE_CLOCK_32000000_HZ;
+ } else {
+ dev_err(&slave->dev,
+ "Unsupported clock base, mclk %d\n",
+ mclk_freq);
+ return -EINVAL;
+ }
+
+ if (mclk_freq % curr_freq) {
+ dev_err(&slave->dev,
+ "mclk %d is not multiple of bus curr_freq %d\n",
+ mclk_freq, curr_freq);
+ return -EINVAL;
+ }
+
+ scale = mclk_freq / curr_freq;
+
+ /*
+ * map scale to Table 90 of SoundWire 1.2 spec - and check
+ * that the scale is a power of two and maximum 64
+ */
+ scale_index = ilog2(scale);
+
+ if (BIT(scale_index) != scale || scale_index > 6) {
+ dev_err(&slave->dev,
+ "No match found for scale %d, bus mclk %d curr_freq %d\n",
+ scale, mclk_freq, curr_freq);
+ return -EINVAL;
+ }
+ scale_index++;
+
+ ret = sdw_write_no_pm(slave, SDW_SCP_BUS_CLOCK_BASE, base);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_BUS_CLOCK_BASE write failed:%d\n", ret);
+ return ret;
+ }
+
+ /* initialize scale for both banks */
+ ret = sdw_write_no_pm(slave, SDW_SCP_BUSCLOCK_SCALE_B0, scale_index);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_BUSCLOCK_SCALE_B0 write failed:%d\n", ret);
+ return ret;
+ }
+ ret = sdw_write_no_pm(slave, SDW_SCP_BUSCLOCK_SCALE_B1, scale_index);
+ if (ret < 0)
+ dev_err(&slave->dev,
+ "SDW_SCP_BUSCLOCK_SCALE_B1 write failed:%d\n", ret);
+
+ dev_dbg(&slave->dev,
+ "Configured bus base %d, scale %d, mclk %d, curr_freq %d\n",
+ base, scale_index, mclk_freq, curr_freq);
+
+ return ret;
+}
+
+static int sdw_initialize_slave(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ int status;
+ int ret;
+ u8 val;
+
+ ret = sdw_slave_set_frequency(slave);
+ if (ret < 0)
+ return ret;
+
+ if (slave->bus->prop.quirks & SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH) {
+ /* Clear bus clash interrupt before enabling interrupt mask */
+ status = sdw_read_no_pm(slave, SDW_SCP_INT1);
+ if (status < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT1 (BUS_CLASH) read failed:%d\n", status);
+ return status;
+ }
+ if (status & SDW_SCP_INT1_BUS_CLASH) {
+ dev_warn(&slave->dev, "Bus clash detected before INT mask is enabled\n");
+ ret = sdw_write_no_pm(slave, SDW_SCP_INT1, SDW_SCP_INT1_BUS_CLASH);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT1 (BUS_CLASH) write failed:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+ if ((slave->bus->prop.quirks & SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY) &&
+ !(slave->prop.quirks & SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY)) {
+ /* Clear parity interrupt before enabling interrupt mask */
+ status = sdw_read_no_pm(slave, SDW_SCP_INT1);
+ if (status < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT1 (PARITY) read failed:%d\n", status);
+ return status;
+ }
+ if (status & SDW_SCP_INT1_PARITY) {
+ dev_warn(&slave->dev, "PARITY error detected before INT mask is enabled\n");
+ ret = sdw_write_no_pm(slave, SDW_SCP_INT1, SDW_SCP_INT1_PARITY);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT1 (PARITY) write failed:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ /*
+ * 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 = slave->prop.scp_int1_mask;
+
+ /* Enable SCP interrupts */
+ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, val, val);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INTMASK1 write failed:%d\n", ret);
+ return ret;
+ }
+
+ /* No need to continue if DP0 is not present */
+ if (!slave->prop.dp0_prop)
+ return 0;
+
+ /* Enable DP0 interrupts */
+ val = prop->dp0_prop->imp_def_interrupts;
+ val |= SDW_DP0_INT_PORT_READY | SDW_DP0_INT_BRA_FAILURE;
+
+ ret = sdw_update_no_pm(slave, SDW_DP0_INTMASK, val, val);
+ if (ret < 0)
+ dev_err(&slave->dev,
+ "SDW_DP0_INTMASK read failed:%d\n", ret);
+ return ret;
+}
+
+static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status)
+{
+ u8 clear, impl_int_mask;
+ int status, status2, ret, count = 0;
+
+ status = sdw_read_no_pm(slave, SDW_DP0_INT);
+ if (status < 0) {
+ dev_err(&slave->dev,
+ "SDW_DP0_INT read failed:%d\n", status);
+ return status;
+ }
+
+ do {
+ clear = status & ~SDW_DP0_INTERRUPTS;
+
+ if (status & SDW_DP0_INT_TEST_FAIL) {
+ dev_err(&slave->dev, "Test fail for port 0\n");
+ clear |= SDW_DP0_INT_TEST_FAIL;
+ }
+
+ /*
+ * Assumption: PORT_READY interrupt will be received only for
+ * ports implementing Channel Prepare state machine (CP_SM)
+ */
+
+ if (status & SDW_DP0_INT_PORT_READY) {
+ complete(&slave->port_ready[0]);
+ clear |= SDW_DP0_INT_PORT_READY;
+ }
+
+ if (status & SDW_DP0_INT_BRA_FAILURE) {
+ dev_err(&slave->dev, "BRA failed\n");
+ clear |= SDW_DP0_INT_BRA_FAILURE;
+ }
+
+ impl_int_mask = SDW_DP0_INT_IMPDEF1 |
+ SDW_DP0_INT_IMPDEF2 | SDW_DP0_INT_IMPDEF3;
+
+ if (status & impl_int_mask) {
+ clear |= impl_int_mask;
+ *slave_status = clear;
+ }
+
+ /* clear the interrupts but don't touch reserved and SDCA_CASCADE fields */
+ ret = sdw_write_no_pm(slave, SDW_DP0_INT, clear);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_DP0_INT write failed:%d\n", ret);
+ return ret;
+ }
+
+ /* Read DP0 interrupt again */
+ status2 = sdw_read_no_pm(slave, SDW_DP0_INT);
+ if (status2 < 0) {
+ dev_err(&slave->dev,
+ "SDW_DP0_INT read failed:%d\n", status2);
+ return status2;
+ }
+ /* filter to limit loop to interrupts identified in the first status read */
+ status &= status2;
+
+ count++;
+
+ /* we can get alerts while processing so keep retrying */
+ } while ((status & SDW_DP0_INTERRUPTS) && (count < SDW_READ_INTR_CLEAR_RETRY));
+
+ if (count == SDW_READ_INTR_CLEAR_RETRY)
+ dev_warn(&slave->dev, "Reached MAX_RETRY on DP0 read\n");
+
+ return ret;
+}
+
+static int sdw_handle_port_interrupt(struct sdw_slave *slave,
+ int port, u8 *slave_status)
+{
+ u8 clear, impl_int_mask;
+ int status, status2, ret, count = 0;
+ u32 addr;
+
+ if (port == 0)
+ return sdw_handle_dp0_interrupt(slave, slave_status);
+
+ addr = SDW_DPN_INT(port);
+ status = sdw_read_no_pm(slave, addr);
+ if (status < 0) {
+ dev_err(&slave->dev,
+ "SDW_DPN_INT read failed:%d\n", status);
+
+ return status;
+ }
+
+ do {
+ clear = status & ~SDW_DPN_INTERRUPTS;
+
+ if (status & SDW_DPN_INT_TEST_FAIL) {
+ dev_err(&slave->dev, "Test fail for port:%d\n", port);
+ clear |= SDW_DPN_INT_TEST_FAIL;
+ }
+
+ /*
+ * Assumption: PORT_READY interrupt will be received only
+ * for ports implementing CP_SM.
+ */
+ if (status & SDW_DPN_INT_PORT_READY) {
+ complete(&slave->port_ready[port]);
+ clear |= SDW_DPN_INT_PORT_READY;
+ }
+
+ impl_int_mask = SDW_DPN_INT_IMPDEF1 |
+ SDW_DPN_INT_IMPDEF2 | SDW_DPN_INT_IMPDEF3;
+
+ if (status & impl_int_mask) {
+ clear |= impl_int_mask;
+ *slave_status = clear;
+ }
+
+ /* clear the interrupt but don't touch reserved fields */
+ ret = sdw_write_no_pm(slave, addr, clear);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_DPN_INT write failed:%d\n", ret);
+ return ret;
+ }
+
+ /* Read DPN interrupt again */
+ status2 = sdw_read_no_pm(slave, addr);
+ if (status2 < 0) {
+ dev_err(&slave->dev,
+ "SDW_DPN_INT read failed:%d\n", status2);
+ return status2;
+ }
+ /* filter to limit loop to interrupts identified in the first status read */
+ status &= status2;
+
+ count++;
+
+ /* we can get alerts while processing so keep retrying */
+ } while ((status & SDW_DPN_INTERRUPTS) && (count < SDW_READ_INTR_CLEAR_RETRY));
+
+ if (count == SDW_READ_INTR_CLEAR_RETRY)
+ dev_warn(&slave->dev, "Reached MAX_RETRY on port read");
+
+ return ret;
+}
+
+static int sdw_handle_slave_alerts(struct sdw_slave *slave)
+{
+ struct sdw_slave_intr_status slave_intr;
+ u8 clear = 0, bit, port_status[15] = {0};
+ int port_num, stat, ret, count = 0;
+ unsigned long port;
+ bool slave_notify;
+ u8 sdca_cascade = 0;
+ u8 buf, buf2[2], _buf, _buf2[2];
+ bool parity_check;
+ bool parity_quirk;
+
+ sdw_modify_slave_status(slave, SDW_SLAVE_ALERT);
+
+ ret = pm_runtime_get_sync(&slave->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err(&slave->dev, "Failed to resume device: %d\n", ret);
+ pm_runtime_put_noidle(&slave->dev);
+ return ret;
+ }
+
+ /* Read Intstat 1, Intstat 2 and Intstat 3 registers */
+ ret = sdw_read_no_pm(slave, SDW_SCP_INT1);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT1 read failed:%d\n", ret);
+ goto io_err;
+ }
+ buf = ret;
+
+ ret = sdw_nread_no_pm(slave, SDW_SCP_INTSTAT2, 2, buf2);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT2/3 read failed:%d\n", ret);
+ goto io_err;
+ }
+
+ if (slave->prop.is_sdca) {
+ ret = sdw_read_no_pm(slave, SDW_DP0_INT);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_DP0_INT read failed:%d\n", ret);
+ goto io_err;
+ }
+ sdca_cascade = ret & SDW_DP0_SDCA_CASCADE;
+ }
+
+ do {
+ slave_notify = false;
+
+ /*
+ * Check parity, bus clash and Slave (impl defined)
+ * interrupt
+ */
+ if (buf & SDW_SCP_INT1_PARITY) {
+ 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) {
+ 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;
+ }
+
+ /*
+ * When bus clash or parity errors are detected, such errors
+ * are unlikely to be recoverable errors.
+ * TODO: In such scenario, reset bus. Make this configurable
+ * via sysfs property with bus reset being the default.
+ */
+
+ if (buf & SDW_SCP_INT1_IMPL_DEF) {
+ 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;
+ }
+
+ /* the SDCA interrupts are cleared in the codec driver .interrupt_callback() */
+ if (sdca_cascade)
+ 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 = FIELD_GET(SDW_SCP_INT1_PORT0_3, port);
+ for_each_set_bit(bit, &port, 8) {
+ sdw_handle_port_interrupt(slave, bit,
+ &port_status[bit]);
+ }
+
+ /* Check if cascade 2 interrupt is present */
+ if (buf & SDW_SCP_INT1_SCP2_CASCADE) {
+ port = buf2[0] & SDW_SCP_INTSTAT2_PORT4_10;
+ for_each_set_bit(bit, &port, 8) {
+ /* scp2 ports start from 4 */
+ port_num = bit + 4;
+ sdw_handle_port_interrupt(slave,
+ port_num,
+ &port_status[port_num]);
+ }
+ }
+
+ /* now check last cascade */
+ if (buf2[0] & SDW_SCP_INTSTAT2_SCP3_CASCADE) {
+ port = buf2[1] & SDW_SCP_INTSTAT3_PORT11_14;
+ for_each_set_bit(bit, &port, 8) {
+ /* scp3 ports start from 11 */
+ port_num = bit + 11;
+ sdw_handle_port_interrupt(slave,
+ port_num,
+ &port_status[port_num]);
+ }
+ }
+
+ /* Update the Slave driver */
+ if (slave_notify) {
+ mutex_lock(&slave->sdw_dev_lock);
+
+ if (slave->probed) {
+ struct device *dev = &slave->dev;
+ struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
+
+ if (drv->ops && drv->ops->interrupt_callback) {
+ slave_intr.sdca_cascade = sdca_cascade;
+ slave_intr.control_port = clear;
+ memcpy(slave_intr.port, &port_status,
+ sizeof(slave_intr.port));
+
+ drv->ops->interrupt_callback(slave, &slave_intr);
+ }
+ }
+
+ mutex_unlock(&slave->sdw_dev_lock);
+ }
+
+ /* Ack interrupt */
+ ret = sdw_write_no_pm(slave, SDW_SCP_INT1, clear);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT1 write failed:%d\n", ret);
+ 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.
+ */
+ ret = sdw_read_no_pm(slave, SDW_SCP_INT1);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT1 recheck read failed:%d\n", ret);
+ goto io_err;
+ }
+ _buf = ret;
+
+ ret = sdw_nread_no_pm(slave, SDW_SCP_INTSTAT2, 2, _buf2);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_SCP_INT2/3 recheck read failed:%d\n", ret);
+ goto io_err;
+ }
+
+ if (slave->prop.is_sdca) {
+ ret = sdw_read_no_pm(slave, SDW_DP0_INT);
+ if (ret < 0) {
+ dev_err(&slave->dev,
+ "SDW_DP0_INT recheck read failed:%d\n", ret);
+ goto io_err;
+ }
+ sdca_cascade = ret & SDW_DP0_SDCA_CASCADE;
+ }
+
+ /*
+ * Make sure no interrupts are pending, but filter to limit loop
+ * to interrupts identified in the first status read
+ */
+ buf &= _buf;
+ buf2[0] &= _buf2[0];
+ buf2[1] &= _buf2[1];
+ stat = buf || buf2[0] || buf2[1] || sdca_cascade;
+
+ /*
+ * Exit loop if Slave is continuously in ALERT state even
+ * after servicing the interrupt multiple times.
+ */
+ count++;
+
+ /* we can get alerts while processing so keep retrying */
+ } while (stat != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
+
+ if (count == SDW_READ_INTR_CLEAR_RETRY)
+ dev_warn(&slave->dev, "Reached MAX_RETRY on alert read\n");
+
+io_err:
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put_autosuspend(&slave->dev);
+
+ return ret;
+}
+
+static int sdw_update_slave_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ int ret = 0;
+
+ mutex_lock(&slave->sdw_dev_lock);
+
+ if (slave->probed) {
+ struct device *dev = &slave->dev;
+ struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
+
+ if (drv->ops && drv->ops->update_status)
+ ret = drv->ops->update_status(slave, status);
+ }
+
+ mutex_unlock(&slave->sdw_dev_lock);
+
+ return ret;
+}
+
+/**
+ * sdw_handle_slave_status() - Handle Slave status
+ * @bus: SDW bus instance
+ * @status: Status for all Slave(s)
+ */
+int sdw_handle_slave_status(struct sdw_bus *bus,
+ enum sdw_slave_status status[])
+{
+ enum sdw_slave_status prev_status;
+ struct sdw_slave *slave;
+ bool attached_initializing, id_programmed;
+ int i, ret = 0;
+
+ /* first check if any Slaves fell off the bus */
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+ mutex_lock(&bus->bus_lock);
+ if (test_bit(i, bus->assigned) == false) {
+ mutex_unlock(&bus->bus_lock);
+ continue;
+ }
+ mutex_unlock(&bus->bus_lock);
+
+ slave = sdw_get_slave(bus, i);
+ if (!slave)
+ continue;
+
+ if (status[i] == SDW_SLAVE_UNATTACHED &&
+ slave->status != SDW_SLAVE_UNATTACHED) {
+ dev_warn(&slave->dev, "Slave %d state check1: UNATTACHED, status was %d\n",
+ i, slave->status);
+ sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
+
+ /* Ensure driver knows that peripheral unattached */
+ ret = sdw_update_slave_status(slave, status[i]);
+ if (ret < 0)
+ dev_warn(&slave->dev, "Update Slave status failed:%d\n", ret);
+ }
+ }
+
+ if (status[0] == SDW_SLAVE_ATTACHED) {
+ dev_dbg(bus->dev, "Slave attached, programming device number\n");
+
+ /*
+ * Programming a device number will have side effects,
+ * so we deal with other devices at a later time.
+ * This relies on those devices reporting ATTACHED, which will
+ * trigger another call to this function. This will only
+ * happen if at least one device ID was programmed.
+ * Error returns from sdw_program_device_num() are currently
+ * ignored because there's no useful recovery that can be done.
+ * Returning the error here could result in the current status
+ * of other devices not being handled, because if no device IDs
+ * were programmed there's nothing to guarantee a status change
+ * to trigger another call to this function.
+ */
+ sdw_program_device_num(bus, &id_programmed);
+ if (id_programmed)
+ return 0;
+ }
+
+ /* Continue to check other slave statuses */
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+ mutex_lock(&bus->bus_lock);
+ if (test_bit(i, bus->assigned) == false) {
+ mutex_unlock(&bus->bus_lock);
+ continue;
+ }
+ mutex_unlock(&bus->bus_lock);
+
+ slave = sdw_get_slave(bus, i);
+ if (!slave)
+ continue;
+
+ attached_initializing = false;
+
+ switch (status[i]) {
+ case SDW_SLAVE_UNATTACHED:
+ if (slave->status == SDW_SLAVE_UNATTACHED)
+ break;
+
+ dev_warn(&slave->dev, "Slave %d state check2: UNATTACHED, status was %d\n",
+ i, slave->status);
+
+ sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
+ break;
+
+ case SDW_SLAVE_ALERT:
+ ret = sdw_handle_slave_alerts(slave);
+ if (ret < 0)
+ dev_err(&slave->dev,
+ "Slave %d alert handling failed: %d\n",
+ i, ret);
+ break;
+
+ case SDW_SLAVE_ATTACHED:
+ if (slave->status == SDW_SLAVE_ATTACHED)
+ break;
+
+ prev_status = slave->status;
+ sdw_modify_slave_status(slave, SDW_SLAVE_ATTACHED);
+
+ if (prev_status == SDW_SLAVE_ALERT)
+ break;
+
+ attached_initializing = true;
+
+ ret = sdw_initialize_slave(slave);
+ if (ret < 0)
+ dev_err(&slave->dev,
+ "Slave %d initialization failed: %d\n",
+ i, ret);
+
+ break;
+
+ default:
+ dev_err(&slave->dev, "Invalid slave %d status:%d\n",
+ i, status[i]);
+ break;
+ }
+
+ ret = sdw_update_slave_status(slave, status[i]);
+ if (ret < 0)
+ dev_err(&slave->dev,
+ "Update Slave status failed:%d\n", ret);
+ if (attached_initializing) {
+ dev_dbg(&slave->dev,
+ "signaling initialization completion for Slave %d\n",
+ slave->dev_num);
+
+ complete_all(&slave->initialization_complete);
+
+ /*
+ * If the manager became pm_runtime active, the peripherals will be
+ * restarted and attach, but their pm_runtime status may remain
+ * suspended. If the 'update_slave_status' callback initiates
+ * any sort of deferred processing, this processing would not be
+ * cancelled on pm_runtime suspend.
+ * To avoid such zombie states, we queue a request to resume.
+ * This would be a no-op in case the peripheral was being resumed
+ * by e.g. the ALSA/ASoC framework.
+ */
+ pm_request_resume(&slave->dev);
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(sdw_handle_slave_status);
+
+void sdw_clear_slave_status(struct sdw_bus *bus, u32 request)
+{
+ struct sdw_slave *slave;
+ int i;
+
+ /* Check all non-zero devices */
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+ mutex_lock(&bus->bus_lock);
+ if (test_bit(i, bus->assigned) == false) {
+ mutex_unlock(&bus->bus_lock);
+ continue;
+ }
+ mutex_unlock(&bus->bus_lock);
+
+ slave = sdw_get_slave(bus, i);
+ if (!slave)
+ continue;
+
+ if (slave->status != SDW_SLAVE_UNATTACHED) {
+ sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
+ slave->first_interrupt_done = false;
+ sdw_update_slave_status(slave, SDW_SLAVE_UNATTACHED);
+ }
+
+ /* keep track of request, used in pm_runtime resume */
+ slave->unattach_request = request;
+ }
+}
+EXPORT_SYMBOL(sdw_clear_slave_status);
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h
new file mode 100644
index 000000000..7631ef5e7
--- /dev/null
+++ b/drivers/soundwire/bus.h
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* Copyright(c) 2015-17 Intel Corporation. */
+
+#ifndef __SDW_BUS_H
+#define __SDW_BUS_H
+
+#define DEFAULT_BANK_SWITCH_TIMEOUT 3000
+#define DEFAULT_PROBE_TIMEOUT 2000
+
+u64 sdw_dmi_override_adr(struct sdw_bus *bus, u64 addr);
+
+#if IS_ENABLED(CONFIG_ACPI)
+int sdw_acpi_find_slaves(struct sdw_bus *bus);
+#else
+static inline int sdw_acpi_find_slaves(struct sdw_bus *bus)
+{
+ return -ENOTSUPP;
+}
+#endif
+
+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);
+
+#ifdef CONFIG_DEBUG_FS
+void sdw_bus_debugfs_init(struct sdw_bus *bus);
+void sdw_bus_debugfs_exit(struct sdw_bus *bus);
+void sdw_slave_debugfs_init(struct sdw_slave *slave);
+void sdw_slave_debugfs_exit(struct sdw_slave *slave);
+void sdw_debugfs_init(void);
+void sdw_debugfs_exit(void);
+#else
+static inline void sdw_bus_debugfs_init(struct sdw_bus *bus) {}
+static inline void sdw_bus_debugfs_exit(struct sdw_bus *bus) {}
+static inline void sdw_slave_debugfs_init(struct sdw_slave *slave) {}
+static inline void sdw_slave_debugfs_exit(struct sdw_slave *slave) {}
+static inline void sdw_debugfs_init(void) {}
+static inline void sdw_debugfs_exit(void) {}
+#endif
+
+enum {
+ SDW_MSG_FLAG_READ = 0,
+ SDW_MSG_FLAG_WRITE,
+};
+
+/**
+ * struct sdw_msg - Message structure
+ * @addr: Register address accessed in the Slave
+ * @len: number of messages
+ * @dev_num: Slave device number
+ * @addr_page1: SCP address page 1 Slave register
+ * @addr_page2: SCP address page 2 Slave register
+ * @flags: transfer flags, indicate if xfer is read or write
+ * @buf: message data buffer
+ * @ssp_sync: Send message at SSP (Stream Synchronization Point)
+ * @page: address requires paging
+ */
+struct sdw_msg {
+ u16 addr;
+ u16 len;
+ u8 dev_num;
+ u8 addr_page1;
+ u8 addr_page2;
+ u8 flags;
+ u8 *buf;
+ bool ssp_sync;
+ bool page;
+};
+
+#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];
+
+int sdw_find_row_index(int row);
+int sdw_find_col_index(int col);
+
+/**
+ * sdw_port_runtime: Runtime port parameters for Master or Slave
+ *
+ * @num: Port number. For audio streams, valid port number ranges from
+ * [1,14]
+ * @ch_mask: Channel mask
+ * @transport_params: Transport parameters
+ * @port_params: Port parameters
+ * @port_node: List node for Master or Slave port_list
+ *
+ * SoundWire spec has no mention of ports for Master interface but the
+ * concept is logically extended.
+ */
+struct sdw_port_runtime {
+ int num;
+ int ch_mask;
+ struct sdw_transport_params transport_params;
+ struct sdw_port_params port_params;
+ struct list_head port_node;
+};
+
+/**
+ * sdw_slave_runtime: Runtime Stream parameters for Slave
+ *
+ * @slave: Slave handle
+ * @direction: Data direction for Slave
+ * @ch_count: Number of channels handled by the Slave for
+ * this stream
+ * @m_rt_node: sdw_master_runtime list node
+ * @port_list: List of Slave Ports configured for this stream
+ */
+struct sdw_slave_runtime {
+ struct sdw_slave *slave;
+ enum sdw_data_direction direction;
+ unsigned int ch_count;
+ struct list_head m_rt_node;
+ struct list_head port_list;
+};
+
+/**
+ * sdw_master_runtime: Runtime stream parameters for Master
+ *
+ * @bus: Bus handle
+ * @stream: Stream runtime handle
+ * @direction: Data direction for Master
+ * @ch_count: Number of channels handled by the Master for
+ * this stream, can be zero.
+ * @slave_rt_list: Slave runtime list
+ * @port_list: List of Master Ports configured for this stream, can be zero.
+ * @stream_node: sdw_stream_runtime master_list node
+ * @bus_node: sdw_bus m_rt_list node
+ */
+struct sdw_master_runtime {
+ struct sdw_bus *bus;
+ struct sdw_stream_runtime *stream;
+ enum sdw_data_direction direction;
+ unsigned int ch_count;
+ struct list_head slave_rt_list;
+ struct list_head port_list;
+ struct list_head stream_node;
+ struct list_head bus_node;
+};
+
+struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
+ enum sdw_data_direction direction,
+ unsigned int port_num);
+int sdw_configure_dpn_intr(struct sdw_slave *slave, int port,
+ bool enable, int mask);
+
+int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg);
+int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg,
+ struct sdw_defer *defer);
+
+#define SDW_READ_INTR_CLEAR_RETRY 10
+
+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;
+}
+
+/* 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
+ */
+#define SDW_UNATTACH_REQUEST_MASTER_RESET BIT(0)
+
+void sdw_clear_slave_status(struct sdw_bus *bus, u32 request);
+int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size);
+
+#endif /* __SDW_BUS_H */
diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c
new file mode 100644
index 000000000..963498db0
--- /dev/null
+++ b/drivers/soundwire/bus_type.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright(c) 2015-17 Intel Corporation.
+
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_domain.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+#include "sysfs_local.h"
+
+/**
+ * sdw_get_device_id - find the matching SoundWire device id
+ * @slave: SoundWire Slave Device
+ * @drv: SoundWire Slave Driver
+ *
+ * The match is done by comparing the mfg_id and part_id from the
+ * struct sdw_device_id.
+ */
+static const struct sdw_device_id *
+sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv)
+{
+ const struct sdw_device_id *id;
+
+ for (id = drv->id_table; id && id->mfg_id; id++)
+ if (slave->id.mfg_id == id->mfg_id &&
+ slave->id.part_id == id->part_id &&
+ (!id->sdw_version ||
+ slave->id.sdw_version == id->sdw_version) &&
+ (!id->class_id ||
+ slave->id.class_id == id->class_id))
+ return id;
+
+ return NULL;
+}
+
+static int sdw_bus_match(struct device *dev, struct device_driver *ddrv)
+{
+ struct sdw_slave *slave;
+ struct sdw_driver *drv;
+ int ret = 0;
+
+ if (is_sdw_slave(dev)) {
+ slave = dev_to_sdw_dev(dev);
+ drv = drv_to_sdw_driver(ddrv);
+
+ ret = !!sdw_get_device_id(slave, drv);
+ }
+ return ret;
+}
+
+int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size)
+{
+ /* modalias is sdw:m<mfg_id>p<part_id>v<version>c<class_id> */
+
+ return snprintf(buf, size, "sdw:m%04Xp%04Xv%02Xc%02X\n",
+ slave->id.mfg_id, slave->id.part_id,
+ slave->id.sdw_version, slave->id.class_id);
+}
+
+int sdw_slave_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ char modalias[32];
+
+ sdw_slave_modalias(slave, modalias, sizeof(modalias));
+
+ if (add_uevent_var(env, "MODALIAS=%s", modalias))
+ return -ENOMEM;
+
+ return 0;
+}
+
+struct bus_type sdw_bus_type = {
+ .name = "soundwire",
+ .match = sdw_bus_match,
+};
+EXPORT_SYMBOL_GPL(sdw_bus_type);
+
+static int sdw_drv_probe(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
+ const struct sdw_device_id *id;
+ const char *name;
+ 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;
+
+ /*
+ * attach to power domain but don't turn on (last arg)
+ */
+ ret = dev_pm_domain_attach(dev, false);
+ if (ret)
+ return ret;
+
+ ret = drv->probe(slave, id);
+ if (ret) {
+ name = drv->name;
+ if (!name)
+ name = drv->driver.name;
+
+ dev_err(dev, "Probe of %s failed: %d\n", name, ret);
+ dev_pm_domain_detach(dev, false);
+ return ret;
+ }
+
+ mutex_lock(&slave->sdw_dev_lock);
+
+ /* device is probed so let's read the properties now */
+ if (drv->ops && drv->ops->read_prop)
+ drv->ops->read_prop(slave);
+
+ /* init the sysfs as we have properties now */
+ ret = sdw_slave_sysfs_init(slave);
+ if (ret < 0)
+ dev_warn(dev, "Slave sysfs init failed:%d\n", ret);
+
+ /*
+ * Check for valid clk_stop_timeout, use DisCo worst case value of
+ * 300ms
+ *
+ * TODO: check the timeouts and driver removal case
+ */
+ if (slave->prop.clk_stop_timeout == 0)
+ slave->prop.clk_stop_timeout = 300;
+
+ slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout,
+ slave->prop.clk_stop_timeout);
+
+ slave->probed = true;
+
+ /*
+ * if the probe happened after the bus was started, notify the codec driver
+ * of the current hardware status to e.g. start the initialization.
+ * Errors are only logged as warnings to avoid failing the probe.
+ */
+ if (drv->ops && drv->ops->update_status) {
+ ret = drv->ops->update_status(slave, slave->status);
+ if (ret < 0)
+ dev_warn(dev, "%s: update_status failed with status %d\n", __func__, ret);
+ }
+
+ mutex_unlock(&slave->sdw_dev_lock);
+
+ dev_dbg(dev, "probe complete\n");
+
+ return 0;
+}
+
+static int sdw_drv_remove(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
+ int ret = 0;
+
+ mutex_lock(&slave->sdw_dev_lock);
+ slave->probed = false;
+ mutex_unlock(&slave->sdw_dev_lock);
+
+ if (drv->remove)
+ ret = drv->remove(slave);
+
+ dev_pm_domain_detach(dev, false);
+
+ return ret;
+}
+
+static void sdw_drv_shutdown(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
+
+ if (drv->shutdown)
+ drv->shutdown(slave);
+}
+
+/**
+ * __sdw_register_driver() - register a SoundWire Slave driver
+ * @drv: driver to register
+ * @owner: owning module/driver
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int __sdw_register_driver(struct sdw_driver *drv, struct module *owner)
+{
+ const char *name;
+
+ drv->driver.bus = &sdw_bus_type;
+
+ if (!drv->probe) {
+ name = drv->name;
+ if (!name)
+ name = drv->driver.name;
+
+ pr_err("driver %s didn't provide SDW probe routine\n", name);
+ return -EINVAL;
+ }
+
+ drv->driver.owner = owner;
+ drv->driver.probe = sdw_drv_probe;
+ drv->driver.remove = sdw_drv_remove;
+ drv->driver.shutdown = sdw_drv_shutdown;
+
+ return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(__sdw_register_driver);
+
+/**
+ * sdw_unregister_driver() - unregisters the SoundWire Slave driver
+ * @drv: driver to unregister
+ */
+void sdw_unregister_driver(struct sdw_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(sdw_unregister_driver);
+
+static int __init sdw_bus_init(void)
+{
+ sdw_debugfs_init();
+ return bus_register(&sdw_bus_type);
+}
+
+static void __exit sdw_bus_exit(void)
+{
+ sdw_debugfs_exit();
+ bus_unregister(&sdw_bus_type);
+}
+
+postcore_initcall(sdw_bus_init);
+module_exit(sdw_bus_exit);
+
+MODULE_DESCRIPTION("SoundWire bus");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c
new file mode 100644
index 000000000..7286c9b3b
--- /dev/null
+++ b/drivers/soundwire/cadence_master.c
@@ -0,0 +1,1865 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+/*
+ * Cadence SoundWire Master module
+ * Used by Master driver
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/interrupt.h>
+#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>
+#include <sound/soc.h>
+#include <linux/workqueue.h>
+#include "bus.h"
+#include "cadence_master.h"
+
+static int interrupt_mask;
+module_param_named(cnds_mcp_int_mask, interrupt_mask, int, 0444);
+MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask");
+
+#define CDNS_MCP_CONFIG 0x0
+
+#define CDNS_MCP_CONFIG_MCMD_RETRY GENMASK(27, 24)
+#define CDNS_MCP_CONFIG_MPREQ_DELAY GENMASK(20, 16)
+#define CDNS_MCP_CONFIG_MMASTER BIT(7)
+#define CDNS_MCP_CONFIG_BUS_REL BIT(6)
+#define CDNS_MCP_CONFIG_SNIFFER BIT(5)
+#define CDNS_MCP_CONFIG_SSPMOD BIT(4)
+#define CDNS_MCP_CONFIG_CMD BIT(3)
+#define CDNS_MCP_CONFIG_OP GENMASK(2, 0)
+#define CDNS_MCP_CONFIG_OP_NORMAL 0
+
+#define CDNS_MCP_CONTROL 0x4
+
+#define CDNS_MCP_CONTROL_RST_DELAY GENMASK(10, 8)
+#define CDNS_MCP_CONTROL_CMD_RST BIT(7)
+#define CDNS_MCP_CONTROL_SOFT_RST BIT(6)
+#define CDNS_MCP_CONTROL_SW_RST BIT(5)
+#define CDNS_MCP_CONTROL_HW_RST BIT(4)
+#define CDNS_MCP_CONTROL_CLK_PAUSE BIT(3)
+#define CDNS_MCP_CONTROL_CLK_STOP_CLR BIT(2)
+#define CDNS_MCP_CONTROL_CMD_ACCEPT BIT(1)
+#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_MASK GENMASK(7, 3)
+
+#define CDNS_MCP_CONFIG_UPDATE 0x18
+#define CDNS_MCP_CONFIG_UPDATE_BIT BIT(0)
+
+#define CDNS_MCP_PHYCTRL 0x1C
+#define CDNS_MCP_SSP_CTRL0 0x20
+#define CDNS_MCP_SSP_CTRL1 0x28
+#define CDNS_MCP_CLK_CTRL0 0x30
+#define CDNS_MCP_CLK_CTRL1 0x38
+#define CDNS_MCP_CLK_MCLKD_MASK GENMASK(7, 0)
+
+#define CDNS_MCP_STAT 0x40
+
+#define CDNS_MCP_STAT_ACTIVE_BANK BIT(20)
+#define CDNS_MCP_STAT_CLK_STOP BIT(16)
+
+#define CDNS_MCP_INTSTAT 0x44
+#define CDNS_MCP_INTMASK 0x48
+
+#define CDNS_MCP_INT_IRQ BIT(31)
+#define CDNS_MCP_INT_RESERVED1 GENMASK(30, 17)
+#define CDNS_MCP_INT_WAKEUP BIT(16)
+#define CDNS_MCP_INT_SLAVE_RSVD BIT(15)
+#define CDNS_MCP_INT_SLAVE_ALERT BIT(14)
+#define CDNS_MCP_INT_SLAVE_ATTACH BIT(13)
+#define CDNS_MCP_INT_SLAVE_NATTACH BIT(12)
+#define CDNS_MCP_INT_SLAVE_MASK GENMASK(15, 12)
+#define CDNS_MCP_INT_DPINT BIT(11)
+#define CDNS_MCP_INT_CTRL_CLASH BIT(10)
+#define CDNS_MCP_INT_DATA_CLASH BIT(9)
+#define CDNS_MCP_INT_PARITY BIT(8)
+#define CDNS_MCP_INT_CMD_ERR BIT(7)
+#define CDNS_MCP_INT_RESERVED2 GENMASK(6, 4)
+#define CDNS_MCP_INT_RX_NE BIT(3)
+#define CDNS_MCP_INT_RX_WL BIT(2)
+#define CDNS_MCP_INT_TXE BIT(1)
+#define CDNS_MCP_INT_TXF BIT(0)
+#define CDNS_MCP_INT_RESERVED (CDNS_MCP_INT_RESERVED1 | CDNS_MCP_INT_RESERVED2)
+
+#define CDNS_MCP_INTSET 0x4C
+
+#define CDNS_MCP_SLAVE_STAT 0x50
+#define CDNS_MCP_SLAVE_STAT_MASK GENMASK(1, 0)
+
+#define CDNS_MCP_SLAVE_INTSTAT0 0x54
+#define CDNS_MCP_SLAVE_INTSTAT1 0x58
+#define CDNS_MCP_SLAVE_INTSTAT_NPRESENT BIT(0)
+#define CDNS_MCP_SLAVE_INTSTAT_ATTACHED BIT(1)
+#define CDNS_MCP_SLAVE_INTSTAT_ALERT BIT(2)
+#define CDNS_MCP_SLAVE_INTSTAT_RESERVED BIT(3)
+#define CDNS_MCP_SLAVE_STATUS_BITS GENMASK(3, 0)
+#define CDNS_MCP_SLAVE_STATUS_NUM 4
+
+#define CDNS_MCP_SLAVE_INTMASK0 0x5C
+#define CDNS_MCP_SLAVE_INTMASK1 0x60
+
+#define CDNS_MCP_SLAVE_INTMASK0_MASK GENMASK(31, 0)
+#define CDNS_MCP_SLAVE_INTMASK1_MASK GENMASK(15, 0)
+
+#define CDNS_MCP_PORT_INTSTAT 0x64
+#define CDNS_MCP_PDI_STAT 0x6C
+
+#define CDNS_MCP_FIFOLEVEL 0x78
+#define CDNS_MCP_FIFOSTAT 0x7C
+#define CDNS_MCP_RX_FIFO_AVAIL GENMASK(5, 0)
+
+#define CDNS_MCP_CMD_BASE 0x80
+#define CDNS_MCP_RESP_BASE 0x80
+/* FIFO can hold 8 commands */
+#define CDNS_MCP_CMD_LEN 8
+#define CDNS_MCP_CMD_WORD_LEN 0x4
+
+#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 GENMASK(23, 8)
+#define CDNS_MCP_CMD_REG_DATA GENMASK(7, 0)
+
+#define CDNS_MCP_CMD_READ 2
+#define CDNS_MCP_CMD_WRITE 3
+
+#define CDNS_MCP_RESP_RDATA GENMASK(15, 8)
+#define CDNS_MCP_RESP_ACK BIT(0)
+#define CDNS_MCP_RESP_NACK BIT(1)
+
+#define CDNS_DP_SIZE 128
+
+#define CDNS_DPN_B0_CONFIG(n) (0x100 + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B0_CH_EN(n) (0x104 + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B0_SAMPLE_CTRL(n) (0x108 + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B0_OFFSET_CTRL(n) (0x10C + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B0_HCTRL(n) (0x110 + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B0_ASYNC_CTRL(n) (0x114 + CDNS_DP_SIZE * (n))
+
+#define CDNS_DPN_B1_CONFIG(n) (0x118 + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B1_CH_EN(n) (0x11C + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B1_SAMPLE_CTRL(n) (0x120 + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B1_OFFSET_CTRL(n) (0x124 + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B1_HCTRL(n) (0x128 + CDNS_DP_SIZE * (n))
+#define CDNS_DPN_B1_ASYNC_CTRL(n) (0x12C + CDNS_DP_SIZE * (n))
+
+#define CDNS_DPN_CONFIG_BPM BIT(18)
+#define CDNS_DPN_CONFIG_BGC GENMASK(17, 16)
+#define CDNS_DPN_CONFIG_WL GENMASK(12, 8)
+#define CDNS_DPN_CONFIG_PORT_DAT GENMASK(3, 2)
+#define CDNS_DPN_CONFIG_PORT_FLOW GENMASK(1, 0)
+
+#define CDNS_DPN_SAMPLE_CTRL_SI GENMASK(15, 0)
+
+#define CDNS_DPN_OFFSET_CTRL_1 GENMASK(7, 0)
+#define CDNS_DPN_OFFSET_CTRL_2 GENMASK(15, 8)
+
+#define CDNS_DPN_HCTRL_HSTOP GENMASK(3, 0)
+#define CDNS_DPN_HCTRL_HSTART GENMASK(7, 4)
+#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)
+
+#define CDNS_PORT_OFFSET 0x80
+
+#define CDNS_PDI_CONFIG(n) (0x1100 + (n) * 16)
+
+#define CDNS_PDI_CONFIG_SOFT_RESET BIT(24)
+#define CDNS_PDI_CONFIG_CHANNEL GENMASK(15, 8)
+#define CDNS_PDI_CONFIG_PORT GENMASK(4, 0)
+
+/* Driver defaults */
+#define CDNS_TX_TIMEOUT 500
+
+#define CDNS_SCP_RX_FIFOLEVEL 0x2
+
+/*
+ * register accessor helpers
+ */
+static inline u32 cdns_readl(struct sdw_cdns *cdns, int offset)
+{
+ return readl(cdns->registers + offset);
+}
+
+static inline void cdns_writel(struct sdw_cdns *cdns, int offset, u32 value)
+{
+ writel(value, cdns->registers + offset);
+}
+
+static inline void cdns_updatel(struct sdw_cdns *cdns,
+ int offset, u32 mask, u32 val)
+{
+ u32 tmp;
+
+ tmp = cdns_readl(cdns, offset);
+ tmp = (tmp & ~mask) | val;
+ cdns_writel(cdns, offset, tmp);
+}
+
+static int cdns_set_wait(struct sdw_cdns *cdns, int offset, u32 mask, u32 value)
+{
+ int timeout = 10;
+ u32 reg_read;
+
+ /* Wait for bit to be set */
+ do {
+ reg_read = readl(cdns->registers + offset);
+ if ((reg_read & mask) == value)
+ return 0;
+
+ timeout--;
+ usleep_range(50, 100);
+ } while (timeout != 0);
+
+ return -ETIMEDOUT;
+}
+
+static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value)
+{
+ writel(value, cdns->registers + offset);
+
+ /* Wait for bit to be self cleared */
+ return cdns_set_wait(cdns, offset, value, 0);
+}
+
+/*
+ * all changes to the MCP_CONFIG, MCP_CONTROL, MCP_CMDCTRL and MCP_PHYCTRL
+ * need to be confirmed with a write to MCP_CONFIG_UPDATE
+ */
+static int cdns_config_update(struct sdw_cdns *cdns)
+{
+ int ret;
+
+ if (sdw_cdns_is_clock_stop(cdns)) {
+ dev_err(cdns->dev, "Cannot program MCP_CONFIG_UPDATE in ClockStopMode\n");
+ return -EINVAL;
+ }
+
+ ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE,
+ CDNS_MCP_CONFIG_UPDATE_BIT);
+ if (ret < 0)
+ dev_err(cdns->dev, "Config update timedout\n");
+
+ return ret;
+}
+
+/*
+ * debugfs
+ */
+#ifdef CONFIG_DEBUG_FS
+
+#define RD_BUF (2 * PAGE_SIZE)
+
+static ssize_t cdns_sprintf(struct sdw_cdns *cdns,
+ char *buf, size_t pos, unsigned int reg)
+{
+ return scnprintf(buf + pos, RD_BUF - pos,
+ "%4x\t%8x\n", reg, cdns_readl(cdns, reg));
+}
+
+static int cdns_reg_show(struct seq_file *s, void *data)
+{
+ struct sdw_cdns *cdns = s->private;
+ char *buf;
+ ssize_t ret;
+ int num_ports;
+ int i, j;
+
+ buf = kzalloc(RD_BUF, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = scnprintf(buf, RD_BUF, "Register Value\n");
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nMCP Registers\n");
+ /* 8 MCP registers */
+ for (i = CDNS_MCP_CONFIG; i <= CDNS_MCP_PHYCTRL; i += sizeof(u32))
+ ret += cdns_sprintf(cdns, buf, ret, i);
+
+ ret += scnprintf(buf + ret, RD_BUF - ret,
+ "\nStatus & Intr Registers\n");
+ /* 13 Status & Intr registers (offsets 0x70 and 0x74 not defined) */
+ for (i = CDNS_MCP_STAT; i <= CDNS_MCP_FIFOSTAT; i += sizeof(u32))
+ ret += cdns_sprintf(cdns, buf, ret, i);
+
+ ret += scnprintf(buf + ret, RD_BUF - ret,
+ "\nSSP & Clk ctrl Registers\n");
+ ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_SSP_CTRL0);
+ ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_SSP_CTRL1);
+ ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_CLK_CTRL0);
+ ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_CLK_CTRL1);
+
+ ret += scnprintf(buf + ret, RD_BUF - ret,
+ "\nDPn B0 Registers\n");
+
+ num_ports = cdns->num_ports;
+
+ for (i = 0; i < num_ports; i++) {
+ ret += scnprintf(buf + ret, RD_BUF - ret,
+ "\nDP-%d\n", i);
+ for (j = CDNS_DPN_B0_CONFIG(i);
+ j < CDNS_DPN_B0_ASYNC_CTRL(i); j += sizeof(u32))
+ ret += cdns_sprintf(cdns, buf, ret, j);
+ }
+
+ ret += scnprintf(buf + ret, RD_BUF - ret,
+ "\nDPn B1 Registers\n");
+ for (i = 0; i < num_ports; i++) {
+ ret += scnprintf(buf + ret, RD_BUF - ret,
+ "\nDP-%d\n", i);
+
+ for (j = CDNS_DPN_B1_CONFIG(i);
+ j < CDNS_DPN_B1_ASYNC_CTRL(i); j += sizeof(u32))
+ ret += cdns_sprintf(cdns, buf, ret, j);
+ }
+
+ ret += scnprintf(buf + ret, RD_BUF - ret,
+ "\nDPn Control Registers\n");
+ for (i = 0; i < num_ports; i++)
+ ret += cdns_sprintf(cdns, buf, ret,
+ CDNS_PORTCTRL + i * CDNS_PORT_OFFSET);
+
+ ret += scnprintf(buf + ret, RD_BUF - ret,
+ "\nPDIn Config Registers\n");
+
+ /* number of PDI and ports is interchangeable */
+ for (i = 0; i < num_ports; i++)
+ ret += cdns_sprintf(cdns, buf, ret, CDNS_PDI_CONFIG(i));
+
+ seq_printf(s, "%s", buf);
+ kfree(buf);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(cdns_reg);
+
+static int cdns_hw_reset(void *data, u64 value)
+{
+ struct sdw_cdns *cdns = data;
+ int ret;
+
+ if (value != 1)
+ return -EINVAL;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ ret = sdw_cdns_exit_reset(cdns);
+
+ dev_dbg(cdns->dev, "link hw_reset done: %d\n", ret);
+
+ return ret;
+}
+
+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_resume_and_get(bus->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(cdns->dev,
+ "pm_runtime_resume_and_get failed in %s, ret %d\n",
+ __func__, ret);
+ 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");
+
+static int cdns_set_pdi_loopback_source(void *data, u64 value)
+{
+ struct sdw_cdns *cdns = data;
+ unsigned int pdi_out_num = cdns->pcm.num_bd + cdns->pcm.num_out;
+
+ if (value > pdi_out_num)
+ return -EINVAL;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ cdns->pdi_loopback_source = value;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(cdns_pdi_loopback_source_fops, NULL, cdns_set_pdi_loopback_source, "%llu\n");
+
+static int cdns_set_pdi_loopback_target(void *data, u64 value)
+{
+ struct sdw_cdns *cdns = data;
+ unsigned int pdi_in_num = cdns->pcm.num_bd + cdns->pcm.num_in;
+
+ if (value > pdi_in_num)
+ return -EINVAL;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ cdns->pdi_loopback_target = value;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(cdns_pdi_loopback_target_fops, NULL, cdns_set_pdi_loopback_target, "%llu\n");
+
+/**
+ * sdw_cdns_debugfs_init() - Cadence debugfs init
+ * @cdns: Cadence instance
+ * @root: debugfs root
+ */
+void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root)
+{
+ debugfs_create_file("cdns-registers", 0400, root, cdns, &cdns_reg_fops);
+
+ 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);
+
+ cdns->pdi_loopback_source = -1;
+ cdns->pdi_loopback_target = -1;
+
+ debugfs_create_file("cdns-pdi-loopback-source", 0200, root, cdns,
+ &cdns_pdi_loopback_source_fops);
+
+ debugfs_create_file("cdns-pdi-loopback-target", 0200, root, cdns,
+ &cdns_pdi_loopback_target_fops);
+
+}
+EXPORT_SYMBOL_GPL(sdw_cdns_debugfs_init);
+
+#endif /* CONFIG_DEBUG_FS */
+
+/*
+ * IO Calls
+ */
+static enum sdw_command_response
+cdns_fill_msg_resp(struct sdw_cdns *cdns,
+ struct sdw_msg *msg, int count, int offset)
+{
+ int nack = 0, no_ack = 0;
+ int i;
+
+ /* check message response */
+ for (i = 0; i < count; i++) {
+ if (!(cdns->response_buf[i] & CDNS_MCP_RESP_ACK)) {
+ no_ack = 1;
+ dev_vdbg(cdns->dev, "Msg Ack not received, cmd %d\n", i);
+ }
+ if (cdns->response_buf[i] & CDNS_MCP_RESP_NACK) {
+ nack = 1;
+ dev_err_ratelimited(cdns->dev, "Msg NACK received, cmd %d\n", i);
+ }
+ }
+
+ if (nack) {
+ dev_err_ratelimited(cdns->dev, "Msg NACKed for Slave %d\n", msg->dev_num);
+ return SDW_CMD_FAIL;
+ }
+
+ if (no_ack) {
+ dev_dbg_ratelimited(cdns->dev, "Msg ignored for Slave %d\n", msg->dev_num);
+ return SDW_CMD_IGNORED;
+ }
+
+ if (msg->flags == SDW_MSG_FLAG_READ) {
+ /* fill response */
+ for (i = 0; i < count; i++)
+ msg->buf[i + offset] = FIELD_GET(CDNS_MCP_RESP_RDATA,
+ cdns->response_buf[i]);
+ }
+
+ return SDW_CMD_OK;
+}
+
+static void cdns_read_response(struct sdw_cdns *cdns)
+{
+ u32 num_resp, cmd_base;
+ int i;
+
+ /* RX_FIFO_AVAIL can be 2 entries more than the FIFO size */
+ BUILD_BUG_ON(ARRAY_SIZE(cdns->response_buf) < CDNS_MCP_CMD_LEN + 2);
+
+ num_resp = cdns_readl(cdns, CDNS_MCP_FIFOSTAT);
+ num_resp &= CDNS_MCP_RX_FIFO_AVAIL;
+ if (num_resp > ARRAY_SIZE(cdns->response_buf)) {
+ dev_warn(cdns->dev, "RX AVAIL %d too long\n", num_resp);
+ num_resp = ARRAY_SIZE(cdns->response_buf);
+ }
+
+ cmd_base = CDNS_MCP_CMD_BASE;
+
+ for (i = 0; i < num_resp; i++) {
+ cdns->response_buf[i] = cdns_readl(cdns, cmd_base);
+ cmd_base += CDNS_MCP_CMD_WORD_LEN;
+ }
+}
+
+static enum sdw_command_response
+_cdns_xfer_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int cmd,
+ int offset, int count, bool defer)
+{
+ unsigned long time;
+ u32 base, i, data;
+ u16 addr;
+
+ /* Program the watermark level for RX FIFO */
+ if (cdns->msg_count != count) {
+ cdns_writel(cdns, CDNS_MCP_FIFOLEVEL, count);
+ cdns->msg_count = count;
+ }
+
+ base = CDNS_MCP_CMD_BASE;
+ addr = msg->addr + offset;
+
+ for (i = 0; i < count; i++) {
+ 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 |= FIELD_PREP(CDNS_MCP_CMD_SSP_TAG, msg->ssp_sync);
+ cdns_writel(cdns, base, data);
+ base += CDNS_MCP_CMD_WORD_LEN;
+ }
+
+ if (defer)
+ return SDW_CMD_OK;
+
+ /* wait for timeout or response */
+ time = wait_for_completion_timeout(&cdns->tx_complete,
+ msecs_to_jiffies(CDNS_TX_TIMEOUT));
+ if (!time) {
+ dev_err(cdns->dev, "IO transfer timed out, cmd %d device %d addr %x len %d\n",
+ cmd, msg->dev_num, msg->addr, msg->len);
+ msg->len = 0;
+
+ /* Drain anything in the RX_FIFO */
+ cdns_read_response(cdns);
+
+ return SDW_CMD_TIMEOUT;
+ }
+
+ return cdns_fill_msg_resp(cdns, msg, count, offset);
+}
+
+static enum sdw_command_response
+cdns_program_scp_addr(struct sdw_cdns *cdns, struct sdw_msg *msg)
+{
+ int nack = 0, no_ack = 0;
+ unsigned long time;
+ u32 data[2], base;
+ int i;
+
+ /* Program the watermark level for RX FIFO */
+ if (cdns->msg_count != CDNS_SCP_RX_FIFOLEVEL) {
+ cdns_writel(cdns, CDNS_MCP_FIFOLEVEL, CDNS_SCP_RX_FIFOLEVEL);
+ cdns->msg_count = CDNS_SCP_RX_FIFOLEVEL;
+ }
+
+ 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] |= 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;
+
+ base = CDNS_MCP_CMD_BASE;
+ cdns_writel(cdns, base, data[0]);
+ base += CDNS_MCP_CMD_WORD_LEN;
+ cdns_writel(cdns, base, data[1]);
+
+ time = wait_for_completion_timeout(&cdns->tx_complete,
+ msecs_to_jiffies(CDNS_TX_TIMEOUT));
+ if (!time) {
+ dev_err(cdns->dev, "SCP Msg trf timed out\n");
+ msg->len = 0;
+ return SDW_CMD_TIMEOUT;
+ }
+
+ /* check response the writes */
+ for (i = 0; i < 2; i++) {
+ if (!(cdns->response_buf[i] & CDNS_MCP_RESP_ACK)) {
+ no_ack = 1;
+ dev_err(cdns->dev, "Program SCP Ack not received\n");
+ if (cdns->response_buf[i] & CDNS_MCP_RESP_NACK) {
+ nack = 1;
+ dev_err(cdns->dev, "Program SCP NACK received\n");
+ }
+ }
+ }
+
+ /* For NACK, NO ack, don't return err if we are in Broadcast mode */
+ if (nack) {
+ dev_err_ratelimited(cdns->dev,
+ "SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
+ return SDW_CMD_FAIL;
+ }
+
+ if (no_ack) {
+ dev_dbg_ratelimited(cdns->dev,
+ "SCP_addrpage ignored for Slave %d\n", msg->dev_num);
+ return SDW_CMD_IGNORED;
+ }
+
+ return SDW_CMD_OK;
+}
+
+static int cdns_prep_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int *cmd)
+{
+ int ret;
+
+ if (msg->page) {
+ ret = cdns_program_scp_addr(cdns, msg);
+ if (ret) {
+ msg->len = 0;
+ return ret;
+ }
+ }
+
+ switch (msg->flags) {
+ case SDW_MSG_FLAG_READ:
+ *cmd = CDNS_MCP_CMD_READ;
+ break;
+
+ case SDW_MSG_FLAG_WRITE:
+ *cmd = CDNS_MCP_CMD_WRITE;
+ break;
+
+ default:
+ dev_err(cdns->dev, "Invalid msg cmd: %d\n", msg->flags);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+enum sdw_command_response
+cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int cmd = 0, ret, i;
+
+ ret = cdns_prep_msg(cdns, msg, &cmd);
+ if (ret)
+ return SDW_CMD_FAIL_OTHER;
+
+ for (i = 0; i < msg->len / CDNS_MCP_CMD_LEN; i++) {
+ ret = _cdns_xfer_msg(cdns, msg, cmd, i * CDNS_MCP_CMD_LEN,
+ CDNS_MCP_CMD_LEN, false);
+ if (ret != SDW_CMD_OK)
+ return ret;
+ }
+
+ if (!(msg->len % CDNS_MCP_CMD_LEN))
+ return SDW_CMD_OK;
+
+ return _cdns_xfer_msg(cdns, msg, cmd, i * CDNS_MCP_CMD_LEN,
+ msg->len % CDNS_MCP_CMD_LEN, false);
+}
+EXPORT_SYMBOL(cdns_xfer_msg);
+
+enum sdw_command_response
+cdns_xfer_msg_defer(struct sdw_bus *bus,
+ struct sdw_msg *msg, struct sdw_defer *defer)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int cmd = 0, ret;
+
+ /* for defer only 1 message is supported */
+ if (msg->len > 1)
+ return -ENOTSUPP;
+
+ ret = cdns_prep_msg(cdns, msg, &cmd);
+ if (ret)
+ return SDW_CMD_FAIL_OTHER;
+
+ cdns->defer = defer;
+ cdns->defer->length = msg->len;
+
+ return _cdns_xfer_msg(cdns, msg, cmd, 0, msg->len, true);
+}
+EXPORT_SYMBOL(cdns_xfer_msg_defer);
+
+enum sdw_command_response
+cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_msg msg;
+
+ /* Create dummy message with valid device number */
+ memset(&msg, 0, sizeof(msg));
+ msg.dev_num = dev_num;
+
+ return cdns_program_scp_addr(cdns, &msg);
+}
+EXPORT_SYMBOL(cdns_reset_page_addr);
+
+u32 cdns_read_ping_status(struct sdw_bus *bus)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+
+ return cdns_readl(cdns, CDNS_MCP_SLAVE_STAT);
+}
+EXPORT_SYMBOL(cdns_read_ping_status);
+
+/*
+ * IRQ handling
+ */
+
+static int cdns_update_slave_status(struct sdw_cdns *cdns,
+ u64 slave_intstat)
+{
+ enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
+ bool is_slave = false;
+ u32 mask;
+ u32 val;
+ int i, set_status;
+
+ memset(status, 0, sizeof(status));
+
+ for (i = 0; i <= SDW_MAX_DEVICES; i++) {
+ mask = (slave_intstat >> (i * CDNS_MCP_SLAVE_STATUS_NUM)) &
+ CDNS_MCP_SLAVE_STATUS_BITS;
+
+ set_status = 0;
+
+ if (mask) {
+ is_slave = true;
+
+ if (mask & CDNS_MCP_SLAVE_INTSTAT_RESERVED) {
+ status[i] = SDW_SLAVE_RESERVED;
+ set_status++;
+ }
+
+ if (mask & CDNS_MCP_SLAVE_INTSTAT_ATTACHED) {
+ status[i] = SDW_SLAVE_ATTACHED;
+ set_status++;
+ }
+
+ if (mask & CDNS_MCP_SLAVE_INTSTAT_ALERT) {
+ status[i] = SDW_SLAVE_ALERT;
+ set_status++;
+ }
+
+ if (mask & CDNS_MCP_SLAVE_INTSTAT_NPRESENT) {
+ status[i] = SDW_SLAVE_UNATTACHED;
+ set_status++;
+ }
+ }
+
+ /*
+ * check that there was a single reported Slave status and when
+ * there is not use the latest status extracted from PING commands
+ */
+ if (set_status != 1) {
+ val = cdns_readl(cdns, CDNS_MCP_SLAVE_STAT);
+ val >>= (i * 2);
+
+ switch (val & 0x3) {
+ case 0:
+ status[i] = SDW_SLAVE_UNATTACHED;
+ break;
+ case 1:
+ status[i] = SDW_SLAVE_ATTACHED;
+ break;
+ case 2:
+ status[i] = SDW_SLAVE_ALERT;
+ break;
+ case 3:
+ default:
+ status[i] = SDW_SLAVE_RESERVED;
+ break;
+ }
+ }
+ }
+
+ if (is_slave)
+ return sdw_handle_slave_status(&cdns->bus, status);
+
+ return 0;
+}
+
+/**
+ * sdw_cdns_irq() - Cadence interrupt handler
+ * @irq: irq number
+ * @dev_id: irq context
+ */
+irqreturn_t sdw_cdns_irq(int irq, void *dev_id)
+{
+ struct sdw_cdns *cdns = dev_id;
+ u32 int_status;
+
+ /* Check if the link is up */
+ if (!cdns->link_up)
+ return IRQ_NONE;
+
+ int_status = cdns_readl(cdns, CDNS_MCP_INTSTAT);
+
+ /* check for reserved values read as zero */
+ if (int_status & CDNS_MCP_INT_RESERVED)
+ return IRQ_NONE;
+
+ if (!(int_status & CDNS_MCP_INT_IRQ))
+ return IRQ_NONE;
+
+ if (int_status & CDNS_MCP_INT_RX_WL) {
+ cdns_read_response(cdns);
+
+ if (cdns->defer) {
+ cdns_fill_msg_resp(cdns, cdns->defer->msg,
+ cdns->defer->length, 0);
+ complete(&cdns->defer->complete);
+ cdns->defer = NULL;
+ } else {
+ complete(&cdns->tx_complete);
+ }
+ }
+
+ if (int_status & CDNS_MCP_INT_PARITY) {
+ /* Parity error detected by Master */
+ dev_err_ratelimited(cdns->dev, "Parity error\n");
+ }
+
+ if (int_status & CDNS_MCP_INT_CTRL_CLASH) {
+ /* Slave is driving bit slot during control word */
+ dev_err_ratelimited(cdns->dev, "Bus clash for control word\n");
+ }
+
+ if (int_status & CDNS_MCP_INT_DATA_CLASH) {
+ /*
+ * Multiple slaves trying to drive bit slot, or issue with
+ * ownership of data bits or Slave gone bonkers
+ */
+ 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;
+
+ /*
+ * 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);
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(sdw_cdns_irq);
+
+/**
+ * cdns_update_slave_status_work - update slave status in a work since we will need to handle
+ * other interrupts eg. CDNS_MCP_INT_RX_WL during the update slave
+ * process.
+ * @work: cdns worker thread
+ */
+static void cdns_update_slave_status_work(struct work_struct *work)
+{
+ struct sdw_cdns *cdns =
+ container_of(work, struct sdw_cdns, work);
+ u32 slave0, slave1;
+ u64 slave_intstat;
+ u32 device0_status;
+ int retry_count = 0;
+
+ /*
+ * Clear main interrupt first so we don't lose any assertions
+ * that happen during this function.
+ */
+ cdns_writel(cdns, CDNS_MCP_INTSTAT, CDNS_MCP_INT_SLAVE_MASK);
+
+ slave0 = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT0);
+ slave1 = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT1);
+
+ /*
+ * Clear the bits before handling so we don't lose any
+ * bits that re-assert.
+ */
+ cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT0, slave0);
+ cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT1, slave1);
+
+ /* combine the two status */
+ slave_intstat = ((u64)slave1 << 32) | slave0;
+
+ dev_dbg_ratelimited(cdns->dev, "Slave status change: 0x%llx\n", slave_intstat);
+
+update_status:
+ cdns_update_slave_status(cdns, slave_intstat);
+
+ /*
+ * When there is more than one peripheral per link, it's
+ * possible that a deviceB becomes attached after we deal with
+ * the attachment of deviceA. Since the hardware does a
+ * logical AND, the attachment of the second device does not
+ * change the status seen by the driver.
+ *
+ * In that case, clearing the registers above would result in
+ * the deviceB never being detected - until a change of status
+ * is observed on the bus.
+ *
+ * To avoid this race condition, re-check if any device0 needs
+ * attention with PING commands. There is no need to check for
+ * ALERTS since they are not allowed until a non-zero
+ * device_number is assigned.
+ *
+ * Do not clear the INTSTAT0/1. While looping to enumerate devices on
+ * #0 there could be status changes on other devices - these must
+ * be kept in the INTSTAT so they can be handled when all #0 devices
+ * have been handled.
+ */
+
+ device0_status = cdns_readl(cdns, CDNS_MCP_SLAVE_STAT);
+ device0_status &= 3;
+
+ if (device0_status == SDW_SLAVE_ATTACHED) {
+ if (retry_count++ < SDW_MAX_DEVICES) {
+ dev_dbg_ratelimited(cdns->dev,
+ "Device0 detected after clearing status, iteration %d\n",
+ retry_count);
+ slave_intstat = CDNS_MCP_SLAVE_INTSTAT_ATTACHED;
+ goto update_status;
+ } else {
+ dev_err_ratelimited(cdns->dev,
+ "Device0 detected after %d iterations\n",
+ retry_count);
+ }
+ }
+
+ /* unmask Slave interrupt now */
+ cdns_updatel(cdns, CDNS_MCP_INTMASK,
+ CDNS_MCP_INT_SLAVE_MASK, CDNS_MCP_INT_SLAVE_MASK);
+
+}
+
+/* paranoia check to make sure self-cleared bits are indeed cleared */
+void sdw_cdns_check_self_clearing_bits(struct sdw_cdns *cdns, const char *string,
+ bool initial_delay, int reset_iterations)
+{
+ u32 mcp_control;
+ u32 mcp_config_update;
+ int i;
+
+ if (initial_delay)
+ usleep_range(1000, 1500);
+
+ mcp_control = cdns_readl(cdns, CDNS_MCP_CONTROL);
+
+ /* the following bits should be cleared immediately */
+ if (mcp_control & CDNS_MCP_CONTROL_CMD_RST)
+ dev_err(cdns->dev, "%s failed: MCP_CONTROL_CMD_RST is not cleared\n", string);
+ if (mcp_control & CDNS_MCP_CONTROL_SOFT_RST)
+ dev_err(cdns->dev, "%s failed: MCP_CONTROL_SOFT_RST is not cleared\n", string);
+ if (mcp_control & CDNS_MCP_CONTROL_SW_RST)
+ dev_err(cdns->dev, "%s failed: MCP_CONTROL_SW_RST is not cleared\n", string);
+ if (mcp_control & CDNS_MCP_CONTROL_CLK_STOP_CLR)
+ dev_err(cdns->dev, "%s failed: MCP_CONTROL_CLK_STOP_CLR is not cleared\n", string);
+ mcp_config_update = cdns_readl(cdns, CDNS_MCP_CONFIG_UPDATE);
+ if (mcp_config_update & CDNS_MCP_CONFIG_UPDATE_BIT)
+ dev_err(cdns->dev, "%s failed: MCP_CONFIG_UPDATE_BIT is not cleared\n", string);
+
+ i = 0;
+ while (mcp_control & CDNS_MCP_CONTROL_HW_RST) {
+ if (i == reset_iterations) {
+ dev_err(cdns->dev, "%s failed: MCP_CONTROL_HW_RST is not cleared\n", string);
+ break;
+ }
+
+ dev_dbg(cdns->dev, "%s: MCP_CONTROL_HW_RST is not cleared at iteration %d\n", string, i);
+ i++;
+
+ usleep_range(1000, 1500);
+ mcp_control = cdns_readl(cdns, CDNS_MCP_CONTROL);
+ }
+
+}
+EXPORT_SYMBOL(sdw_cdns_check_self_clearing_bits);
+
+/*
+ * init routines
+ */
+
+/**
+ * sdw_cdns_exit_reset() - Program reset parameters and start bus operations
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_exit_reset(struct sdw_cdns *cdns)
+{
+ /* keep reset delay unchanged to 4096 cycles */
+
+ /* use hardware generated reset */
+ cdns_updatel(cdns, CDNS_MCP_CONTROL,
+ CDNS_MCP_CONTROL_HW_RST,
+ CDNS_MCP_CONTROL_HW_RST);
+
+ /* commit changes */
+ cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE,
+ CDNS_MCP_CONFIG_UPDATE_BIT,
+ CDNS_MCP_CONFIG_UPDATE_BIT);
+
+ /* don't wait here */
+ return 0;
+
+}
+EXPORT_SYMBOL(sdw_cdns_exit_reset);
+
+/**
+ * cdns_enable_slave_interrupts() - Enable SDW slave interrupts
+ * @cdns: Cadence instance
+ * @state: boolean for true/false
+ */
+static void cdns_enable_slave_interrupts(struct sdw_cdns *cdns, bool state)
+{
+ u32 mask;
+
+ mask = cdns_readl(cdns, CDNS_MCP_INTMASK);
+ if (state)
+ mask |= CDNS_MCP_INT_SLAVE_MASK;
+ else
+ mask &= ~CDNS_MCP_INT_SLAVE_MASK;
+
+ cdns_writel(cdns, CDNS_MCP_INTMASK, mask);
+}
+
+/**
+ * sdw_cdns_enable_interrupt() - Enable SDW interrupts
+ * @cdns: Cadence instance
+ * @state: True if we are trying to enable interrupt.
+ */
+int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state)
+{
+ u32 slave_intmask0 = 0;
+ u32 slave_intmask1 = 0;
+ u32 mask = 0;
+
+ if (!state)
+ goto update_masks;
+
+ slave_intmask0 = CDNS_MCP_SLAVE_INTMASK0_MASK;
+ slave_intmask1 = CDNS_MCP_SLAVE_INTMASK1_MASK;
+
+ /* enable detection of all slave state changes */
+ mask = CDNS_MCP_INT_SLAVE_MASK;
+
+ /* enable detection of bus issues */
+ mask |= CDNS_MCP_INT_CTRL_CLASH | CDNS_MCP_INT_DATA_CLASH |
+ CDNS_MCP_INT_PARITY;
+
+ /* 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;
+
+ /*
+ * CDNS_MCP_INT_IRQ needs to be set otherwise all previous
+ * settings are irrelevant
+ */
+ mask |= CDNS_MCP_INT_IRQ;
+
+ if (interrupt_mask) /* parameter override */
+ mask = interrupt_mask;
+
+update_masks:
+ /* clear slave interrupt status before enabling interrupt */
+ if (state) {
+ u32 slave_state;
+
+ slave_state = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT0);
+ cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT0, slave_state);
+ 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);
+ cdns_writel(cdns, CDNS_MCP_INTMASK, mask);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_enable_interrupt);
+
+static int cdns_allocate_pdi(struct sdw_cdns *cdns,
+ struct sdw_cdns_pdi **stream,
+ u32 num, u32 pdi_offset)
+{
+ struct sdw_cdns_pdi *pdi;
+ int i;
+
+ if (!num)
+ return 0;
+
+ pdi = devm_kcalloc(cdns->dev, num, sizeof(*pdi), GFP_KERNEL);
+ if (!pdi)
+ return -ENOMEM;
+
+ for (i = 0; i < num; i++) {
+ pdi[i].num = i + pdi_offset;
+ }
+
+ *stream = pdi;
+ return 0;
+}
+
+/**
+ * sdw_cdns_pdi_init() - PDI initialization routine
+ *
+ * @cdns: Cadence instance
+ * @config: Stream configurations
+ */
+int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
+ struct sdw_cdns_stream_config config)
+{
+ struct sdw_cdns_streams *stream;
+ int offset;
+ int ret;
+
+ cdns->pcm.num_bd = config.pcm_bd;
+ cdns->pcm.num_in = config.pcm_in;
+ cdns->pcm.num_out = config.pcm_out;
+
+ /* Allocate PDIs for PCMs */
+ stream = &cdns->pcm;
+
+ /* we allocate PDI0 and PDI1 which are used for Bulk */
+ offset = 0;
+
+ ret = cdns_allocate_pdi(cdns, &stream->bd,
+ stream->num_bd, offset);
+ if (ret)
+ return ret;
+
+ offset += stream->num_bd;
+
+ ret = cdns_allocate_pdi(cdns, &stream->in,
+ stream->num_in, offset);
+ if (ret)
+ return ret;
+
+ offset += stream->num_in;
+
+ ret = cdns_allocate_pdi(cdns, &stream->out,
+ stream->num_out, offset);
+ if (ret)
+ return ret;
+
+ /* Update total number of PCM PDIs */
+ stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out;
+ cdns->num_ports = stream->num_pdi;
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_pdi_init);
+
+static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols)
+{
+ u32 val;
+ int c;
+ int r;
+
+ r = sdw_find_row_index(n_rows);
+ c = sdw_find_col_index(n_cols);
+
+ val = FIELD_PREP(CDNS_MCP_FRAME_SHAPE_ROW_MASK, r);
+ val |= FIELD_PREP(CDNS_MCP_FRAME_SHAPE_COL_MASK, c);
+
+ return val;
+}
+
+static void cdns_init_clock_ctrl(struct sdw_cdns *cdns)
+{
+ struct sdw_bus *bus = &cdns->bus;
+ struct sdw_master_prop *prop = &bus->prop;
+ u32 val;
+ u32 ssp_interval;
+ int divider;
+
+ /* Set clock divider */
+ divider = (prop->mclk_freq / prop->max_clk_freq) - 1;
+
+ cdns_updatel(cdns, CDNS_MCP_CLK_CTRL0,
+ CDNS_MCP_CLK_MCLKD_MASK, divider);
+ cdns_updatel(cdns, CDNS_MCP_CLK_CTRL1,
+ CDNS_MCP_CLK_MCLKD_MASK, divider);
+
+ /*
+ * Frame shape changes after initialization have to be done
+ * with the bank switch mechanism
+ */
+ val = cdns_set_initial_frame_shape(prop->default_row,
+ prop->default_col);
+ cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, val);
+
+ /* Set SSP interval to default value */
+ ssp_interval = prop->default_frame_rate / SDW_CADENCE_GSYNC_HZ;
+ cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, ssp_interval);
+ cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, ssp_interval);
+}
+
+/**
+ * sdw_cdns_init() - Cadence initialization
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_init(struct sdw_cdns *cdns)
+{
+ u32 val;
+
+ cdns_init_clock_ctrl(cdns);
+
+ sdw_cdns_check_self_clearing_bits(cdns, __func__, false, 0);
+
+ /* reset msg_count to default value of FIFOLEVEL */
+ cdns->msg_count = cdns_readl(cdns, CDNS_MCP_FIFOLEVEL);
+
+ /* flush command FIFOs */
+ cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_RST,
+ CDNS_MCP_CONTROL_CMD_RST);
+
+ /* Set cmd accept mode */
+ cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT,
+ CDNS_MCP_CONTROL_CMD_ACCEPT);
+
+ /* Configure mcp config */
+ val = cdns_readl(cdns, CDNS_MCP_CONFIG);
+
+ /* enable bus operations with clock and data */
+ val &= ~CDNS_MCP_CONFIG_OP;
+ val |= CDNS_MCP_CONFIG_OP_NORMAL;
+
+ /* Set cmd mode for Tx and Rx cmds */
+ val &= ~CDNS_MCP_CONFIG_CMD;
+
+ /* Disable sniffer mode */
+ val &= ~CDNS_MCP_CONFIG_SNIFFER;
+
+ /* Disable auto bus release */
+ val &= ~CDNS_MCP_CONFIG_BUS_REL;
+
+ if (cdns->bus.multi_link)
+ /* Set Multi-master mode to take gsync into account */
+ val |= CDNS_MCP_CONFIG_MMASTER;
+
+ /* leave frame delay to hardware default of 0x1F */
+
+ /* leave command retry to hardware default of 0 */
+
+ cdns_writel(cdns, CDNS_MCP_CONFIG, val);
+
+ /* changes will be committed later */
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_init);
+
+int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params)
+{
+ struct sdw_master_prop *prop = &bus->prop;
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int mcp_clkctrl_off;
+ int divider;
+
+ if (!params->curr_dr_freq) {
+ dev_err(cdns->dev, "NULL curr_dr_freq\n");
+ return -EINVAL;
+ }
+
+ divider = prop->mclk_freq * SDW_DOUBLE_RATE_FACTOR /
+ params->curr_dr_freq;
+ divider--; /* divider is 1/(N+1) */
+
+ if (params->next_bank)
+ mcp_clkctrl_off = CDNS_MCP_CLK_CTRL1;
+ else
+ mcp_clkctrl_off = CDNS_MCP_CLK_CTRL0;
+
+ cdns_updatel(cdns, mcp_clkctrl_off, CDNS_MCP_CLK_MCLKD_MASK, divider);
+
+ return 0;
+}
+EXPORT_SYMBOL(cdns_bus_conf);
+
+static int cdns_port_params(struct sdw_bus *bus,
+ struct sdw_port_params *p_params, unsigned int bank)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int dpn_config_off_source;
+ int dpn_config_off_target;
+ int target_num = p_params->num;
+ int source_num = p_params->num;
+ bool override = false;
+ int dpn_config;
+
+ if (target_num == cdns->pdi_loopback_target &&
+ cdns->pdi_loopback_source != -1) {
+ source_num = cdns->pdi_loopback_source;
+ override = true;
+ }
+
+ if (bank) {
+ dpn_config_off_source = CDNS_DPN_B1_CONFIG(source_num);
+ dpn_config_off_target = CDNS_DPN_B1_CONFIG(target_num);
+ } else {
+ dpn_config_off_source = CDNS_DPN_B0_CONFIG(source_num);
+ dpn_config_off_target = CDNS_DPN_B0_CONFIG(target_num);
+ }
+
+ dpn_config = cdns_readl(cdns, dpn_config_off_source);
+
+ /* use port params if there is no loopback, otherwise use source as is */
+ if (!override) {
+ 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_target, dpn_config);
+
+ return 0;
+}
+
+static int cdns_transport_params(struct sdw_bus *bus,
+ struct sdw_transport_params *t_params,
+ enum sdw_reg_bank bank)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int dpn_config;
+ int dpn_config_off_source;
+ int dpn_config_off_target;
+ int dpn_hctrl;
+ int dpn_hctrl_off_source;
+ int dpn_hctrl_off_target;
+ int dpn_offsetctrl;
+ int dpn_offsetctrl_off_source;
+ int dpn_offsetctrl_off_target;
+ int dpn_samplectrl;
+ int dpn_samplectrl_off_source;
+ int dpn_samplectrl_off_target;
+ int source_num = t_params->port_num;
+ int target_num = t_params->port_num;
+ bool override = false;
+
+ if (target_num == cdns->pdi_loopback_target &&
+ cdns->pdi_loopback_source != -1) {
+ source_num = cdns->pdi_loopback_source;
+ override = true;
+ }
+
+ /*
+ * Note: Only full data port is supported on the Master side for
+ * both PCM and PDM ports.
+ */
+
+ if (bank) {
+ dpn_config_off_source = CDNS_DPN_B1_CONFIG(source_num);
+ dpn_hctrl_off_source = CDNS_DPN_B1_HCTRL(source_num);
+ dpn_offsetctrl_off_source = CDNS_DPN_B1_OFFSET_CTRL(source_num);
+ dpn_samplectrl_off_source = CDNS_DPN_B1_SAMPLE_CTRL(source_num);
+
+ dpn_config_off_target = CDNS_DPN_B1_CONFIG(target_num);
+ dpn_hctrl_off_target = CDNS_DPN_B1_HCTRL(target_num);
+ dpn_offsetctrl_off_target = CDNS_DPN_B1_OFFSET_CTRL(target_num);
+ dpn_samplectrl_off_target = CDNS_DPN_B1_SAMPLE_CTRL(target_num);
+
+ } else {
+ dpn_config_off_source = CDNS_DPN_B0_CONFIG(source_num);
+ dpn_hctrl_off_source = CDNS_DPN_B0_HCTRL(source_num);
+ dpn_offsetctrl_off_source = CDNS_DPN_B0_OFFSET_CTRL(source_num);
+ dpn_samplectrl_off_source = CDNS_DPN_B0_SAMPLE_CTRL(source_num);
+
+ dpn_config_off_target = CDNS_DPN_B0_CONFIG(target_num);
+ dpn_hctrl_off_target = CDNS_DPN_B0_HCTRL(target_num);
+ dpn_offsetctrl_off_target = CDNS_DPN_B0_OFFSET_CTRL(target_num);
+ dpn_samplectrl_off_target = CDNS_DPN_B0_SAMPLE_CTRL(target_num);
+ }
+
+ dpn_config = cdns_readl(cdns, dpn_config_off_source);
+ if (!override) {
+ 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_target, dpn_config);
+
+ if (!override) {
+ dpn_offsetctrl = 0;
+ 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);
+ } else {
+ dpn_offsetctrl = cdns_readl(cdns, dpn_offsetctrl_off_source);
+ }
+ cdns_writel(cdns, dpn_offsetctrl_off_target, dpn_offsetctrl);
+
+ if (!override) {
+ dpn_hctrl = 0;
+ 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);
+ } else {
+ dpn_hctrl = cdns_readl(cdns, dpn_hctrl_off_source);
+ }
+ cdns_writel(cdns, dpn_hctrl_off_target, dpn_hctrl);
+
+ if (!override)
+ dpn_samplectrl = t_params->sample_interval - 1;
+ else
+ dpn_samplectrl = cdns_readl(cdns, dpn_samplectrl_off_source);
+ cdns_writel(cdns, dpn_samplectrl_off_target, dpn_samplectrl);
+
+ return 0;
+}
+
+static int cdns_port_enable(struct sdw_bus *bus,
+ struct sdw_enable_ch *enable_ch, unsigned int bank)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int dpn_chnen_off, ch_mask;
+
+ if (bank)
+ dpn_chnen_off = CDNS_DPN_B1_CH_EN(enable_ch->port_num);
+ else
+ dpn_chnen_off = CDNS_DPN_B0_CH_EN(enable_ch->port_num);
+
+ ch_mask = enable_ch->ch_mask * enable_ch->enable;
+ cdns_writel(cdns, dpn_chnen_off, ch_mask);
+
+ return 0;
+}
+
+static const struct sdw_master_port_ops cdns_port_ops = {
+ .dpn_set_port_params = cdns_port_params,
+ .dpn_set_port_transport_params = cdns_transport_params,
+ .dpn_port_enable_ch = cdns_port_enable,
+};
+
+/**
+ * sdw_cdns_is_clock_stop: Check clock status
+ *
+ * @cdns: Cadence instance
+ */
+bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns)
+{
+ return !!(cdns_readl(cdns, CDNS_MCP_STAT) & CDNS_MCP_STAT_CLK_STOP);
+}
+EXPORT_SYMBOL(sdw_cdns_is_clock_stop);
+
+/**
+ * sdw_cdns_clock_stop: Cadence clock stop configuration routine
+ *
+ * @cdns: Cadence instance
+ * @block_wake: prevent wakes if required by the platform
+ */
+int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake)
+{
+ bool slave_present = false;
+ struct sdw_slave *slave;
+ int ret;
+
+ sdw_cdns_check_self_clearing_bits(cdns, __func__, false, 0);
+
+ /* Check suspend status */
+ if (sdw_cdns_is_clock_stop(cdns)) {
+ dev_dbg(cdns->dev, "Clock is already stopped\n");
+ return 0;
+ }
+
+ /*
+ * Before entering clock stop we mask the Slave
+ * interrupts. This helps avoid having to deal with e.g. a
+ * Slave becoming UNATTACHED while the clock is being stopped
+ */
+ cdns_enable_slave_interrupts(cdns, false);
+
+ /*
+ * For specific platforms, it is required to be able to put
+ * master into a state in which it ignores wake-up trials
+ * in clock stop state
+ */
+ if (block_wake)
+ cdns_updatel(cdns, CDNS_MCP_CONTROL,
+ CDNS_MCP_CONTROL_BLOCK_WAKEUP,
+ CDNS_MCP_CONTROL_BLOCK_WAKEUP);
+
+ list_for_each_entry(slave, &cdns->bus.slaves, node) {
+ if (slave->status == SDW_SLAVE_ATTACHED ||
+ slave->status == SDW_SLAVE_ALERT) {
+ slave_present = true;
+ break;
+ }
+ }
+
+ /* commit changes */
+ ret = cdns_config_update(cdns);
+ if (ret < 0) {
+ dev_err(cdns->dev, "%s: config_update failed\n", __func__);
+ return ret;
+ }
+
+ /* Prepare slaves for clock stop */
+ if (slave_present) {
+ ret = sdw_bus_prep_clk_stop(&cdns->bus);
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(cdns->dev, "prepare clock stop failed %d\n", ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Enter clock stop mode and only report errors if there are
+ * Slave devices present (ALERT or ATTACHED)
+ */
+ ret = sdw_bus_clk_stop(&cdns->bus);
+ if (ret < 0 && slave_present && ret != -ENODATA) {
+ dev_err(cdns->dev, "bus clock stop failed %d\n", ret);
+ return ret;
+ }
+
+ ret = cdns_set_wait(cdns, CDNS_MCP_STAT,
+ CDNS_MCP_STAT_CLK_STOP,
+ CDNS_MCP_STAT_CLK_STOP);
+ if (ret < 0)
+ dev_err(cdns->dev, "Clock stop failed %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(sdw_cdns_clock_stop);
+
+/**
+ * sdw_cdns_clock_restart: Cadence PM clock restart configuration routine
+ *
+ * @cdns: Cadence instance
+ * @bus_reset: context may be lost while in low power modes and the bus
+ * may require a Severe Reset and re-enumeration after a wake.
+ */
+int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset)
+{
+ int ret;
+
+ /* unmask Slave interrupts that were masked when stopping the clock */
+ cdns_enable_slave_interrupts(cdns, true);
+
+ ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL,
+ CDNS_MCP_CONTROL_CLK_STOP_CLR);
+ if (ret < 0) {
+ dev_err(cdns->dev, "Couldn't exit from clock stop\n");
+ return ret;
+ }
+
+ ret = cdns_set_wait(cdns, CDNS_MCP_STAT, CDNS_MCP_STAT_CLK_STOP, 0);
+ if (ret < 0) {
+ dev_err(cdns->dev, "clock stop exit failed %d\n", ret);
+ return ret;
+ }
+
+ cdns_updatel(cdns, CDNS_MCP_CONTROL,
+ CDNS_MCP_CONTROL_BLOCK_WAKEUP, 0);
+
+ cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT,
+ CDNS_MCP_CONTROL_CMD_ACCEPT);
+
+ if (!bus_reset) {
+
+ /* enable bus operations with clock and data */
+ cdns_updatel(cdns, CDNS_MCP_CONFIG,
+ CDNS_MCP_CONFIG_OP,
+ CDNS_MCP_CONFIG_OP_NORMAL);
+
+ ret = cdns_config_update(cdns);
+ if (ret < 0) {
+ dev_err(cdns->dev, "%s: config_update failed\n", __func__);
+ return ret;
+ }
+
+ ret = sdw_bus_exit_clk_stop(&cdns->bus);
+ if (ret < 0)
+ dev_err(cdns->dev, "bus failed to exit clock stop %d\n", ret);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(sdw_cdns_clock_restart);
+
+/**
+ * sdw_cdns_probe() - Cadence probe routine
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_probe(struct sdw_cdns *cdns)
+{
+ init_completion(&cdns->tx_complete);
+ cdns->bus.port_ops = &cdns_port_ops;
+
+ INIT_WORK(&cdns->work, cdns_update_slave_status_work);
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_probe);
+
+int cdns_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, int direction)
+{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_cdns_dai_runtime *dai_runtime;
+
+ if (stream) {
+ /* first paranoia check */
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ dai_runtime = dai->playback_dma_data;
+ else
+ dai_runtime = dai->capture_dma_data;
+
+ if (dai_runtime) {
+ dev_err(dai->dev,
+ "dai_runtime already allocated for dai %s\n",
+ dai->name);
+ return -EINVAL;
+ }
+
+ /* allocate and set dai_runtime info */
+ dai_runtime = kzalloc(sizeof(*dai_runtime), GFP_KERNEL);
+ if (!dai_runtime)
+ return -ENOMEM;
+
+ dai_runtime->stream_type = SDW_STREAM_PCM;
+
+ dai_runtime->bus = &cdns->bus;
+ dai_runtime->link_id = cdns->instance;
+
+ dai_runtime->stream = stream;
+
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ dai->playback_dma_data = dai_runtime;
+ else
+ dai->capture_dma_data = dai_runtime;
+ } else {
+ /* for NULL stream we release allocated dai_runtime */
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ kfree(dai->playback_dma_data);
+ dai->playback_dma_data = NULL;
+ } else {
+ kfree(dai->capture_dma_data);
+ dai->capture_dma_data = NULL;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL(cdns_set_sdw_stream);
+
+/**
+ * cdns_find_pdi() - Find a free PDI
+ *
+ * @cdns: Cadence instance
+ * @offset: Starting offset
+ * @num: Number of PDIs
+ * @pdi: PDI instances
+ * @dai_id: DAI id
+ *
+ * Find a PDI for a given PDI array. The PDI num and dai_id are
+ * expected to match, return NULL otherwise.
+ */
+static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns,
+ unsigned int offset,
+ unsigned int num,
+ struct sdw_cdns_pdi *pdi,
+ int dai_id)
+{
+ int i;
+
+ for (i = offset; i < offset + num; i++)
+ if (pdi[i].num == dai_id)
+ return &pdi[i];
+
+ return NULL;
+}
+
+/**
+ * sdw_cdns_config_stream: Configure a stream
+ *
+ * @cdns: Cadence instance
+ * @ch: Channel count
+ * @dir: Data direction
+ * @pdi: PDI to be used
+ */
+void sdw_cdns_config_stream(struct sdw_cdns *cdns,
+ u32 ch, u32 dir, struct sdw_cdns_pdi *pdi)
+{
+ u32 offset, val = 0;
+
+ 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 | CDNS_PORTCTRL_TEST_FAILED,
+ val);
+
+ val = pdi->num;
+ val |= CDNS_PDI_CONFIG_SOFT_RESET;
+ 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);
+
+/**
+ * sdw_cdns_alloc_pdi() - Allocate a PDI
+ *
+ * @cdns: Cadence instance
+ * @stream: Stream to be allocated
+ * @ch: Channel count
+ * @dir: Data direction
+ * @dai_id: DAI id
+ */
+struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns,
+ struct sdw_cdns_streams *stream,
+ u32 ch, u32 dir, int dai_id)
+{
+ struct sdw_cdns_pdi *pdi = NULL;
+
+ if (dir == SDW_DATA_DIR_RX)
+ pdi = cdns_find_pdi(cdns, 0, stream->num_in, stream->in,
+ dai_id);
+ else
+ pdi = cdns_find_pdi(cdns, 0, stream->num_out, stream->out,
+ dai_id);
+
+ /* check if we found a PDI, else find in bi-directional */
+ if (!pdi)
+ pdi = cdns_find_pdi(cdns, 2, stream->num_bd, stream->bd,
+ dai_id);
+
+ if (pdi) {
+ pdi->l_ch_num = 0;
+ pdi->h_ch_num = ch - 1;
+ pdi->dir = dir;
+ pdi->ch_count = ch;
+ }
+
+ return pdi;
+}
+EXPORT_SYMBOL(sdw_cdns_alloc_pdi);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Cadence Soundwire Library");
diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h
new file mode 100644
index 000000000..fea3a9055
--- /dev/null
+++ b/drivers/soundwire/cadence_master.h
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* Copyright(c) 2015-17 Intel Corporation. */
+#include <sound/soc.h>
+
+#ifndef __SDW_CADENCE_H
+#define __SDW_CADENCE_H
+
+#define SDW_CADENCE_GSYNC_KHZ 4 /* 4 kHz */
+#define SDW_CADENCE_GSYNC_HZ (SDW_CADENCE_GSYNC_KHZ * 1000)
+
+/*
+ * The Cadence IP supports up to 32 entries in the FIFO, though implementations
+ * can configure the IP to have a smaller FIFO.
+ */
+#define CDNS_MCP_IP_MAX_CMD_LEN 32
+
+/**
+ * struct sdw_cdns_pdi: PDI (Physical Data Interface) instance
+ *
+ * @num: pdi number
+ * @intel_alh_id: link identifier
+ * @l_ch_num: low channel for PDI
+ * @h_ch_num: high channel for PDI
+ * @ch_count: total channel count for PDI
+ * @dir: data direction
+ * @type: stream type, (only PCM supported)
+ */
+struct sdw_cdns_pdi {
+ int num;
+ int intel_alh_id;
+ int l_ch_num;
+ int h_ch_num;
+ int ch_count;
+ enum sdw_data_direction dir;
+ enum sdw_stream_type type;
+};
+
+/**
+ * struct sdw_cdns_streams: Cadence stream data structure
+ *
+ * @num_bd: number of bidirectional streams
+ * @num_in: number of input streams
+ * @num_out: number of output streams
+ * @num_ch_bd: number of bidirectional stream channels
+ * @num_ch_bd: number of input stream channels
+ * @num_ch_bd: number of output stream channels
+ * @num_pdi: total number of PDIs
+ * @bd: bidirectional streams
+ * @in: input streams
+ * @out: output streams
+ */
+struct sdw_cdns_streams {
+ unsigned int num_bd;
+ unsigned int num_in;
+ unsigned int num_out;
+ unsigned int num_ch_bd;
+ unsigned int num_ch_in;
+ unsigned int num_ch_out;
+ unsigned int num_pdi;
+ struct sdw_cdns_pdi *bd;
+ struct sdw_cdns_pdi *in;
+ struct sdw_cdns_pdi *out;
+};
+
+/**
+ * struct sdw_cdns_stream_config: stream configuration
+ *
+ * @pcm_bd: number of bidirectional PCM streams supported
+ * @pcm_in: number of input PCM streams supported
+ * @pcm_out: number of output PCM streams supported
+ */
+struct sdw_cdns_stream_config {
+ unsigned int pcm_bd;
+ unsigned int pcm_in;
+ unsigned int pcm_out;
+};
+
+/**
+ * struct sdw_cdns_dai_runtime: Cadence DAI runtime data
+ *
+ * @name: SoundWire stream name
+ * @stream: stream runtime
+ * @pdi: PDI used for this dai
+ * @bus: Bus handle
+ * @stream_type: Stream type
+ * @link_id: Master link id
+ * @suspended: status set when suspended, to be used in .prepare
+ * @paused: status set in .trigger, to be used in suspend
+ */
+struct sdw_cdns_dai_runtime {
+ char *name;
+ struct sdw_stream_runtime *stream;
+ struct sdw_cdns_pdi *pdi;
+ struct sdw_bus *bus;
+ enum sdw_stream_type stream_type;
+ int link_id;
+ bool suspended;
+ bool paused;
+};
+
+/**
+ * struct sdw_cdns - Cadence driver context
+ * @dev: Linux device
+ * @bus: Bus handle
+ * @instance: instance number
+ * @response_buf: SoundWire response buffer
+ * @tx_complete: Tx completion
+ * @defer: Defer pointer
+ * @ports: Data ports
+ * @num_ports: Total number of data ports
+ * @pcm: PCM streams
+ * @registers: Cadence registers
+ * @link_up: Link status
+ * @msg_count: Messages sent on bus
+ */
+struct sdw_cdns {
+ struct device *dev;
+ struct sdw_bus bus;
+ unsigned int instance;
+
+ /*
+ * The datasheet says the RX FIFO AVAIL can be 2 entries more
+ * than the FIFO capacity, so allow for this.
+ */
+ u32 response_buf[CDNS_MCP_IP_MAX_CMD_LEN + 2];
+
+ struct completion tx_complete;
+ struct sdw_defer *defer;
+
+ struct sdw_cdns_port *ports;
+ int num_ports;
+
+ struct sdw_cdns_streams pcm;
+
+ int pdi_loopback_source;
+ int pdi_loopback_target;
+
+ void __iomem *registers;
+
+ bool link_up;
+ unsigned int msg_count;
+ bool interrupt_enabled;
+
+ struct work_struct work;
+
+ struct list_head list;
+};
+
+#define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus)
+
+/* Exported symbols */
+
+int sdw_cdns_probe(struct sdw_cdns *cdns);
+extern struct sdw_master_ops sdw_cdns_master_ops;
+
+irqreturn_t sdw_cdns_irq(int irq, void *dev_id);
+irqreturn_t sdw_cdns_thread(int irq, void *dev_id);
+
+int sdw_cdns_init(struct sdw_cdns *cdns);
+int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
+ struct sdw_cdns_stream_config config);
+int sdw_cdns_exit_reset(struct sdw_cdns *cdns);
+int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state);
+
+bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns);
+int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake);
+int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset);
+
+#ifdef CONFIG_DEBUG_FS
+void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root);
+#endif
+
+struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns,
+ struct sdw_cdns_streams *stream,
+ u32 ch, u32 dir, int dai_id);
+void sdw_cdns_config_stream(struct sdw_cdns *cdns,
+ u32 ch, u32 dir, struct sdw_cdns_pdi *pdi);
+
+enum sdw_command_response
+cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num);
+
+enum sdw_command_response
+cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg);
+
+enum sdw_command_response
+cdns_xfer_msg_defer(struct sdw_bus *bus,
+ struct sdw_msg *msg, struct sdw_defer *defer);
+
+u32 cdns_read_ping_status(struct sdw_bus *bus);
+
+int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params);
+
+int cdns_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, int direction);
+
+void sdw_cdns_check_self_clearing_bits(struct sdw_cdns *cdns, const char *string,
+ bool initial_delay, int reset_iterations);
+
+#endif /* __SDW_CADENCE_H */
diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c
new file mode 100644
index 000000000..49900cd20
--- /dev/null
+++ b/drivers/soundwire/debugfs.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2017-2019 Intel Corporation.
+
+#include <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include "bus.h"
+
+static struct dentry *sdw_debugfs_root;
+
+void sdw_bus_debugfs_init(struct sdw_bus *bus)
+{
+ char name[16];
+
+ if (!sdw_debugfs_root)
+ return;
+
+ /* create the debugfs master-N */
+ snprintf(name, sizeof(name), "master-%d-%d", bus->id, bus->link_id);
+ bus->debugfs = debugfs_create_dir(name, sdw_debugfs_root);
+}
+
+void sdw_bus_debugfs_exit(struct sdw_bus *bus)
+{
+ debugfs_remove_recursive(bus->debugfs);
+}
+
+#define RD_BUF (3 * PAGE_SIZE)
+
+static ssize_t sdw_sprintf(struct sdw_slave *slave,
+ char *buf, size_t pos, unsigned int reg)
+{
+ int value;
+
+ value = sdw_read(slave, reg);
+
+ if (value < 0)
+ return scnprintf(buf + pos, RD_BUF - pos, "%3x\tXX\n", reg);
+ else
+ return scnprintf(buf + pos, RD_BUF - pos,
+ "%3x\t%2x\n", reg, value);
+}
+
+static int sdw_slave_reg_show(struct seq_file *s_file, void *data)
+{
+ struct sdw_slave *slave = s_file->private;
+ char *buf;
+ ssize_t ret;
+ int i, j;
+
+ buf = kzalloc(RD_BUF, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = scnprintf(buf, RD_BUF, "Register Value\n");
+
+ /* DP0 non-banked registers */
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nDP0\n");
+ for (i = SDW_DP0_INT; i <= SDW_DP0_PREPARECTRL; i++)
+ ret += sdw_sprintf(slave, buf, ret, i);
+
+ /* DP0 Bank 0 registers */
+ ret += scnprintf(buf + ret, RD_BUF - ret, "Bank0\n");
+ ret += sdw_sprintf(slave, buf, ret, SDW_DP0_CHANNELEN);
+ for (i = SDW_DP0_SAMPLECTRL1; i <= SDW_DP0_LANECTRL; i++)
+ ret += sdw_sprintf(slave, buf, ret, i);
+
+ /* DP0 Bank 1 registers */
+ ret += scnprintf(buf + ret, RD_BUF - ret, "Bank1\n");
+ ret += sdw_sprintf(slave, buf, ret,
+ SDW_DP0_CHANNELEN + SDW_BANK1_OFFSET);
+ for (i = SDW_DP0_SAMPLECTRL1 + SDW_BANK1_OFFSET;
+ i <= SDW_DP0_LANECTRL + SDW_BANK1_OFFSET; i++)
+ ret += sdw_sprintf(slave, buf, ret, i);
+
+ /* SCP registers */
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nSCP\n");
+ for (i = SDW_SCP_INT1; i <= SDW_SCP_BANKDELAY; i++)
+ ret += sdw_sprintf(slave, buf, ret, i);
+ for (i = SDW_SCP_DEVID_0; i <= SDW_SCP_DEVID_5; i++)
+ ret += sdw_sprintf(slave, buf, ret, i);
+
+ /*
+ * SCP Bank 0/1 registers are read-only and cannot be
+ * retrieved from the Slave. The Master typically keeps track
+ * of the current frame size so the information can be found
+ * in other places
+ */
+
+ /* DP1..14 registers */
+ for (i = 1; SDW_VALID_PORT_RANGE(i); i++) {
+
+ /* DPi registers */
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nDP%d\n", i);
+ for (j = SDW_DPN_INT(i); j <= SDW_DPN_PREPARECTRL(i); j++)
+ ret += sdw_sprintf(slave, buf, ret, j);
+
+ /* DPi Bank0 registers */
+ ret += scnprintf(buf + ret, RD_BUF - ret, "Bank0\n");
+ for (j = SDW_DPN_CHANNELEN_B0(i);
+ j <= SDW_DPN_LANECTRL_B0(i); j++)
+ ret += sdw_sprintf(slave, buf, ret, j);
+
+ /* DPi Bank1 registers */
+ ret += scnprintf(buf + ret, RD_BUF - ret, "Bank1\n");
+ for (j = SDW_DPN_CHANNELEN_B1(i);
+ j <= SDW_DPN_LANECTRL_B1(i); j++)
+ ret += sdw_sprintf(slave, buf, ret, j);
+ }
+
+ seq_printf(s_file, "%s", buf);
+ kfree(buf);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(sdw_slave_reg);
+
+void sdw_slave_debugfs_init(struct sdw_slave *slave)
+{
+ struct dentry *master;
+ struct dentry *d;
+ char name[32];
+
+ master = slave->bus->debugfs;
+
+ /* create the debugfs slave-name */
+ snprintf(name, sizeof(name), "%s", dev_name(&slave->dev));
+ d = debugfs_create_dir(name, master);
+
+ debugfs_create_file("registers", 0400, d, slave, &sdw_slave_reg_fops);
+
+ slave->debugfs = d;
+}
+
+void sdw_slave_debugfs_exit(struct sdw_slave *slave)
+{
+ debugfs_remove_recursive(slave->debugfs);
+}
+
+void sdw_debugfs_init(void)
+{
+ sdw_debugfs_root = debugfs_create_dir("soundwire", NULL);
+}
+
+void sdw_debugfs_exit(void)
+{
+ debugfs_remove_recursive(sdw_debugfs_root);
+}
diff --git a/drivers/soundwire/dmi-quirks.c b/drivers/soundwire/dmi-quirks.c
new file mode 100644
index 000000000..9ebdd0cd0
--- /dev/null
+++ b/drivers/soundwire/dmi-quirks.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2021 Intel Corporation.
+
+/*
+ * Soundwire DMI quirks
+ */
+
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/soundwire/sdw.h>
+#include "bus.h"
+
+struct adr_remap {
+ u64 adr;
+ u64 remapped_adr;
+};
+
+/*
+ * Some TigerLake devices based on an initial Intel BIOS do not expose
+ * the correct _ADR in the DSDT.
+ * Remap the bad _ADR values to the ones reported by hardware
+ */
+static const struct adr_remap intel_tgl_bios[] = {
+ {
+ 0x000010025D070100ull,
+ 0x000020025D071100ull
+ },
+ {
+ 0x000110025d070100ull,
+ 0x000120025D130800ull
+ },
+ {}
+};
+
+/*
+ * The initial version of the Dell SKU 0A3E did not expose the devices
+ * on the correct links.
+ */
+static const struct adr_remap dell_sku_0A3E[] = {
+ /* rt715 on link0 */
+ {
+ 0x00020025d071100ull,
+ 0x00021025d071500ull
+ },
+ /* rt711 on link1 */
+ {
+ 0x000120025d130800ull,
+ 0x000120025d071100ull,
+ },
+ /* rt1308 on link2 */
+ {
+ 0x000220025d071500ull,
+ 0x000220025d130800ull
+ },
+ {}
+};
+
+/*
+ * The HP Omen 16-k0005TX does not expose the correct version of RT711 on link0
+ * and does not expose a RT1316 on link3
+ */
+static const struct adr_remap hp_omen_16[] = {
+ /* rt711-sdca on link0 */
+ {
+ 0x000020025d071100ull,
+ 0x000030025d071101ull
+ },
+ /* rt1316-sdca on link3 */
+ {
+ 0x000120025d071100ull,
+ 0x000330025d131601ull
+ },
+ {}
+};
+
+/*
+ * Intel NUC M15 LAPRC510 and LAPRC710
+ */
+static const struct adr_remap intel_rooks_county[] = {
+ /* rt711-sdca on link0 */
+ {
+ 0x000020025d071100ull,
+ 0x000030025d071101ull
+ },
+ /* rt1316-sdca on link2 */
+ {
+ 0x000120025d071100ull,
+ 0x000230025d131601ull
+ },
+ {}
+};
+
+static const struct dmi_system_id adr_remap_quirk_table[] = {
+ /* TGL devices */
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Conv"),
+ },
+ .driver_data = (void *)intel_tgl_bios,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_BOARD_NAME, "8709"),
+ },
+ .driver_data = (void *)intel_tgl_bios,
+ },
+ {
+ /* quirk used for NUC15 'Bishop County' LAPBC510 and LAPBC710 skews */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LAPBC"),
+ },
+ .driver_data = (void *)intel_tgl_bios,
+ },
+ {
+ /* quirk used for NUC15 LAPBC710 skew */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_BOARD_NAME, "LAPBC710"),
+ },
+ .driver_data = (void *)intel_tgl_bios,
+ },
+ {
+ /* quirk used for NUC15 'Rooks County' LAPRC510 and LAPRC710 skews */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LAPRC"),
+ },
+ .driver_data = (void *)intel_rooks_county,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A3E")
+ },
+ .driver_data = (void *)dell_sku_0A3E,
+ },
+ /* ADL devices */
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "OMEN by HP Gaming Laptop 16"),
+ },
+ .driver_data = (void *)hp_omen_16,
+ },
+ {}
+};
+
+u64 sdw_dmi_override_adr(struct sdw_bus *bus, u64 addr)
+{
+ const struct dmi_system_id *dmi_id;
+
+ /* check if any address remap quirk applies */
+ dmi_id = dmi_first_match(adr_remap_quirk_table);
+ if (dmi_id) {
+ struct adr_remap *map;
+
+ for (map = dmi_id->driver_data; map->adr; map++) {
+ if (map->adr == addr) {
+ dev_dbg(bus->dev, "remapped _ADR 0x%llx as 0x%llx\n",
+ addr, map->remapped_adr);
+ addr = map->remapped_adr;
+ break;
+ }
+ }
+ }
+
+ return addr;
+}
diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c
new file mode 100644
index 000000000..f7c66083a
--- /dev/null
+++ b/drivers/soundwire/generic_bandwidth_allocation.c
@@ -0,0 +1,428 @@
+// 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_PKG_PER_PORT, 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;
+
+ 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) {
+
+ 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_PKG_PER_PORT, 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;
+ 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;
+ 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) {
+ dev_err(bus->dev, "%s: could not find clock value for bandwidth %d\n",
+ __func__, bus->params.bandwidth);
+ return -EINVAL;
+ }
+
+ ret = sdw_select_row_col(bus, curr_dr_freq);
+ if (ret < 0) {
+ dev_err(bus->dev, "%s: could not find frame configuration for bus dr_freq %d\n",
+ __func__, curr_dr_freq);
+ 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)
+ 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\n", 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
new file mode 100644
index 000000000..89f8cab3f
--- /dev/null
+++ b/drivers/soundwire/intel.c
@@ -0,0 +1,2045 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+/*
+ * Soundwire Intel Master Driver
+ */
+
+#include <linux/acpi.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/auxiliary_bus.h>
+#include <sound/pcm_params.h>
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
+#include "cadence_master.h"
+#include "bus.h"
+#include "intel.h"
+
+/* IDA min selected to avoid conflicts with HDaudio/iDISP SDI values */
+#define INTEL_DEV_NUM_IDA_MIN 4
+
+#define INTEL_MASTER_SUSPEND_DELAY_MS 3000
+#define INTEL_MASTER_RESET_ITERATIONS 10
+
+/*
+ * 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)");
+
+enum intel_pdi_type {
+ INTEL_PDI_IN = 0,
+ INTEL_PDI_OUT = 1,
+ INTEL_PDI_BD = 2,
+};
+
+#define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns)
+
+/*
+ * Read, write helpers for HW registers
+ */
+static inline int intel_readl(void __iomem *base, int offset)
+{
+ return readl(base + offset);
+}
+
+static inline void intel_writel(void __iomem *base, int offset, int value)
+{
+ writel(value, base + offset);
+}
+
+static inline u16 intel_readw(void __iomem *base, int offset)
+{
+ return readw(base + offset);
+}
+
+static inline void intel_writew(void __iomem *base, int offset, u16 value)
+{
+ writew(value, base + offset);
+}
+
+static int intel_wait_bit(void __iomem *base, int offset, u32 mask, u32 target)
+{
+ int timeout = 10;
+ u32 reg_read;
+
+ do {
+ reg_read = readl(base + offset);
+ if ((reg_read & mask) == target)
+ return 0;
+
+ timeout--;
+ usleep_range(50, 100);
+ } while (timeout != 0);
+
+ return -EAGAIN;
+}
+
+static int intel_clear_bit(void __iomem *base, int offset, u32 value, u32 mask)
+{
+ writel(value, base + offset);
+ return intel_wait_bit(base, offset, mask, 0);
+}
+
+static int intel_set_bit(void __iomem *base, int offset, u32 value, u32 mask)
+{
+ writel(value, base + offset);
+ return intel_wait_bit(base, offset, mask, mask);
+}
+
+/*
+ * debugfs
+ */
+#ifdef CONFIG_DEBUG_FS
+
+#define RD_BUF (2 * PAGE_SIZE)
+
+static ssize_t intel_sprintf(void __iomem *mem, bool l,
+ char *buf, size_t pos, unsigned int reg)
+{
+ int value;
+
+ if (l)
+ value = intel_readl(mem, reg);
+ else
+ value = intel_readw(mem, reg);
+
+ return scnprintf(buf + pos, RD_BUF - pos, "%4x\t%4x\n", reg, value);
+}
+
+static int intel_reg_show(struct seq_file *s_file, void *data)
+{
+ struct sdw_intel *sdw = s_file->private;
+ void __iomem *s = sdw->link_res->shim;
+ void __iomem *a = sdw->link_res->alh;
+ char *buf;
+ ssize_t ret;
+ int i, j;
+ unsigned int links, reg;
+
+ buf = kzalloc(RD_BUF, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ links = intel_readl(s, SDW_SHIM_LCAP) & SDW_SHIM_LCAP_LCOUNT_MASK;
+
+ ret = scnprintf(buf, RD_BUF, "Register Value\n");
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nShim\n");
+
+ for (i = 0; i < links; i++) {
+ reg = SDW_SHIM_LCAP + i * 4;
+ ret += intel_sprintf(s, true, buf, ret, reg);
+ }
+
+ for (i = 0; i < links; i++) {
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nLink%d\n", i);
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLSCAP(i));
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS0CM(i));
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS1CM(i));
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS2CM(i));
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS3CM(i));
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSCAP(i));
+
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\n PCMSyCH registers\n");
+
+ /*
+ * the value 10 is the number of PDIs. We will need a
+ * cleanup to remove hard-coded Intel configurations
+ * from cadence_master.c
+ */
+ for (j = 0; j < 10; j++) {
+ ret += intel_sprintf(s, false, buf, ret,
+ SDW_SHIM_PCMSYCHM(i, j));
+ ret += intel_sprintf(s, false, buf, ret,
+ SDW_SHIM_PCMSYCHC(i, j));
+ }
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\n IOCTL, CTMCTL\n");
+
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_IOCTL(i));
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTMCTL(i));
+ }
+
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nWake registers\n");
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKEEN);
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKESTS);
+
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nALH STRMzCFG\n");
+ for (i = 0; i < SDW_ALH_NUM_STREAMS; i++)
+ ret += intel_sprintf(a, true, buf, ret, SDW_ALH_STRMZCFG(i));
+
+ seq_printf(s_file, "%s", buf);
+ kfree(buf);
+
+ return 0;
+}
+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;
+
+ if (!root)
+ return;
+
+ sdw->debugfs = debugfs_create_dir("intel-sdw", root);
+
+ 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);
+}
+
+static void intel_debugfs_exit(struct sdw_intel *sdw)
+{
+ debugfs_remove_recursive(sdw->debugfs);
+}
+#else
+static void intel_debugfs_init(struct sdw_intel *sdw) {}
+static void intel_debugfs_exit(struct sdw_intel *sdw) {}
+#endif /* CONFIG_DEBUG_FS */
+
+/*
+ * shim ops
+ */
+/* this needs to be called with shim_lock */
+static void intel_shim_glue_to_master_ip(struct sdw_intel *sdw)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ unsigned int link_id = sdw->instance;
+ u16 ioctl;
+
+ /* Switch to MIP from Glue logic */
+ ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id));
+
+ ioctl &= ~(SDW_SHIM_IOCTL_DOE);
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ ioctl &= ~(SDW_SHIM_IOCTL_DO);
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ ioctl |= (SDW_SHIM_IOCTL_MIF);
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ ioctl &= ~(SDW_SHIM_IOCTL_BKE);
+ ioctl &= ~(SDW_SHIM_IOCTL_COE);
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ /* at this point Master IP has full control of the I/Os */
+}
+
+/* this needs to be called with shim_lock */
+static void intel_shim_master_ip_to_glue(struct sdw_intel *sdw)
+{
+ unsigned int link_id = sdw->instance;
+ void __iomem *shim = sdw->link_res->shim;
+ u16 ioctl;
+
+ /* Glue logic */
+ ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id));
+ ioctl |= SDW_SHIM_IOCTL_BKE;
+ ioctl |= SDW_SHIM_IOCTL_COE;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ ioctl &= ~(SDW_SHIM_IOCTL_MIF);
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ /* at this point Integration Glue has full control of the I/Os */
+}
+
+/* this needs to be called with shim_lock */
+static void intel_shim_init(struct sdw_intel *sdw)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ unsigned int link_id = sdw->instance;
+ u16 ioctl = 0, act = 0;
+
+ /* Initialize Shim */
+ ioctl |= SDW_SHIM_IOCTL_BKE;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ ioctl |= SDW_SHIM_IOCTL_WPDD;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ ioctl |= SDW_SHIM_IOCTL_DO;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ ioctl |= SDW_SHIM_IOCTL_DOE;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ usleep_range(10, 15);
+
+ intel_shim_glue_to_master_ip(sdw);
+
+ 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);
+ usleep_range(10, 15);
+}
+
+static int intel_shim_check_wake(struct sdw_intel *sdw)
+{
+ void __iomem *shim;
+ u16 wake_sts;
+
+ shim = sdw->link_res->shim;
+ wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS);
+
+ return wake_sts & BIT(sdw->instance);
+}
+
+static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ unsigned int link_id = sdw->instance;
+ u16 wake_en, wake_sts;
+
+ mutex_lock(sdw->link_res->shim_lock);
+ wake_en = intel_readw(shim, SDW_SHIM_WAKEEN);
+
+ if (wake_enable) {
+ /* Enable the wakeup */
+ wake_en |= (SDW_SHIM_WAKEEN_ENABLE << link_id);
+ intel_writew(shim, SDW_SHIM_WAKEEN, wake_en);
+ } else {
+ /* Disable the wake up interrupt */
+ wake_en &= ~(SDW_SHIM_WAKEEN_ENABLE << link_id);
+ intel_writew(shim, SDW_SHIM_WAKEEN, wake_en);
+
+ /* Clear wake status */
+ wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS);
+ wake_sts |= (SDW_SHIM_WAKESTS_STATUS << link_id);
+ intel_writew(shim, SDW_SHIM_WAKESTS, wake_sts);
+ }
+ mutex_unlock(sdw->link_res->shim_lock);
+}
+
+static int intel_link_power_up(struct sdw_intel *sdw)
+{
+ unsigned int link_id = sdw->instance;
+ void __iomem *shim = sdw->link_res->shim;
+ u32 *shim_mask = sdw->link_res->shim_mask;
+ struct sdw_bus *bus = &sdw->cdns.bus;
+ struct sdw_master_prop *prop = &bus->prop;
+ u32 spa_mask, cpa_mask;
+ u32 link_control;
+ int ret = 0;
+ u32 syncprd;
+ u32 sync_reg;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ /*
+ * The hardware relies on an internal counter, typically 4kHz,
+ * to generate the SoundWire SSP - which defines a 'safe'
+ * synchronization point between commands and audio transport
+ * and allows for multi link synchronization. The SYNCPRD value
+ * is only dependent on the oscillator clock provided to
+ * the IP, so adjust based on _DSD properties reported in DSDT
+ * tables. The values reported are based on either 24MHz
+ * (CNL/CML) or 38.4 MHz (ICL/TGL+).
+ */
+ if (prop->mclk_freq % 6000000)
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4;
+ else
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24;
+
+ if (!*shim_mask) {
+ dev_dbg(sdw->cdns.dev, "powering up all links\n");
+
+ /* we first need to program the SyncPRD/CPU registers */
+ dev_dbg(sdw->cdns.dev,
+ "first link up, programming SYNCPRD\n");
+
+ /* set SyncPRD period */
+ sync_reg = intel_readl(shim, SDW_SHIM_SYNC);
+ 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);
+
+ /* 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;
+ }
+
+ /* SyncCPU will change once link is active */
+ ret = intel_wait_bit(shim, SDW_SHIM_SYNC,
+ SDW_SHIM_SYNC_SYNCCPU, 0);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev,
+ "Failed to set SHIM_SYNC: %d\n", ret);
+ goto out;
+ }
+ }
+
+ *shim_mask |= BIT(link_id);
+
+ sdw->cdns.link_up = true;
+
+ intel_shim_init(sdw);
+
+out:
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ return ret;
+}
+
+static int intel_link_power_down(struct sdw_intel *sdw)
+{
+ 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;
+ int ret = 0;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ if (!(*shim_mask & BIT(link_id)))
+ dev_err(sdw->cdns.dev,
+ "%s: Unbalanced power-up/down calls\n", __func__);
+
+ sdw->cdns.link_up = false;
+
+ intel_shim_master_ip_to_glue(sdw);
+
+ *shim_mask &= ~BIT(link_id);
+
+ if (!*shim_mask) {
+
+ dev_dbg(sdw->cdns.dev, "powering down all links\n");
+
+ /* 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);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: could not power down link\n", __func__);
+
+ /*
+ * we leave the sdw->cdns.link_up flag as false since we've disabled
+ * the link at this point and cannot handle interrupts any longer.
+ */
+ }
+ }
+
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ return ret;
+}
+
+static void intel_shim_sync_arm(struct sdw_intel *sdw)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ u32 sync_reg;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ /* update SYNC register */
+ sync_reg = intel_readl(shim, SDW_SHIM_SYNC);
+ sync_reg |= (SDW_SHIM_SYNC_CMDSYNC << sdw->instance);
+ intel_writel(shim, SDW_SHIM_SYNC, sync_reg);
+
+ mutex_unlock(sdw->link_res->shim_lock);
+}
+
+static int intel_shim_sync_go_unlocked(struct sdw_intel *sdw)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ u32 sync_reg;
+ int ret;
+
+ /* Read SYNC register */
+ sync_reg = intel_readl(shim, SDW_SHIM_SYNC);
+
+ /*
+ * Set SyncGO bit to synchronously trigger a bank switch for
+ * all the masters. A write to SYNCGO bit clears CMDSYNC bit for all
+ * the Masters.
+ */
+ sync_reg |= SDW_SHIM_SYNC_SYNCGO;
+
+ ret = intel_clear_bit(shim, SDW_SHIM_SYNC, sync_reg,
+ SDW_SHIM_SYNC_SYNCGO);
+
+ if (ret < 0)
+ dev_err(sdw->cdns.dev, "SyncGO clear failed: %d\n", ret);
+
+ 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
+ */
+static void intel_pdi_init(struct sdw_intel *sdw,
+ struct sdw_cdns_stream_config *config)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ unsigned int link_id = sdw->instance;
+ int pcm_cap;
+
+ /* PCM Stream Capability */
+ pcm_cap = intel_readw(shim, SDW_SHIM_PCMSCAP(link_id));
+
+ 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);
+}
+
+static int
+intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ unsigned int link_id = sdw->instance;
+ int count;
+
+ count = intel_readw(shim, SDW_SHIM_PCMSYCHC(link_id, pdi_num));
+
+ /*
+ * WORKAROUND: on all existing Intel controllers, pdi
+ * number 2 reports channel count as 1 even though it
+ * supports 8 channels. Performing hardcoding for pdi
+ * number 2.
+ */
+ if (pdi_num == 2)
+ count = 7;
+
+ /* zero based values for channel count in register */
+ count++;
+
+ return count;
+}
+
+static int intel_pdi_get_ch_update(struct sdw_intel *sdw,
+ struct sdw_cdns_pdi *pdi,
+ unsigned int num_pdi,
+ unsigned int *num_ch)
+{
+ int i, ch_count = 0;
+
+ for (i = 0; i < num_pdi; i++) {
+ pdi->ch_count = intel_pdi_get_ch_cap(sdw, pdi->num);
+ ch_count += pdi->ch_count;
+ pdi++;
+ }
+
+ *num_ch = ch_count;
+ return 0;
+}
+
+static int intel_pdi_stream_ch_update(struct sdw_intel *sdw,
+ struct sdw_cdns_streams *stream)
+{
+ intel_pdi_get_ch_update(sdw, stream->bd, stream->num_bd,
+ &stream->num_ch_bd);
+
+ intel_pdi_get_ch_update(sdw, stream->in, stream->num_in,
+ &stream->num_ch_in);
+
+ intel_pdi_get_ch_update(sdw, stream->out, stream->num_out,
+ &stream->num_ch_out);
+
+ return 0;
+}
+
+static int intel_pdi_ch_update(struct sdw_intel *sdw)
+{
+ intel_pdi_stream_ch_update(sdw, &sdw->cdns.pcm);
+
+ return 0;
+}
+
+static void
+intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ unsigned int link_id = sdw->instance;
+ int pdi_conf = 0;
+
+ /* the Bulk and PCM streams are not contiguous */
+ pdi->intel_alh_id = (link_id * 16) + pdi->num + 3;
+ if (pdi->num >= 2)
+ pdi->intel_alh_id += 2;
+
+ /*
+ * Program stream parameters to stream SHIM register
+ * This is applicable for PCM stream only.
+ */
+ if (pdi->type != SDW_STREAM_PCM)
+ return;
+
+ if (pdi->dir == SDW_DATA_DIR_RX)
+ pdi_conf |= SDW_SHIM_PCMSYCM_DIR;
+ else
+ pdi_conf &= ~(SDW_SHIM_PCMSYCM_DIR);
+
+ 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);
+}
+
+static void
+intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
+{
+ void __iomem *alh = sdw->link_res->alh;
+ unsigned int link_id = sdw->instance;
+ unsigned int conf;
+
+ /* the Bulk and PCM streams are not contiguous */
+ pdi->intel_alh_id = (link_id * 16) + pdi->num + 3;
+ if (pdi->num >= 2)
+ pdi->intel_alh_id += 2;
+
+ /* Program Stream config ALH register */
+ conf = intel_readl(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id));
+
+ 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);
+}
+
+static int intel_params_stream(struct sdw_intel *sdw,
+ int stream,
+ struct snd_soc_dai *dai,
+ struct snd_pcm_hw_params *hw_params,
+ int link_id, int alh_stream_id)
+{
+ struct sdw_intel_link_res *res = sdw->link_res;
+ struct sdw_intel_stream_params_data params_data;
+
+ params_data.stream = stream; /* direction */
+ params_data.dai = dai;
+ params_data.hw_params = hw_params;
+ params_data.link_id = link_id;
+ params_data.alh_stream_id = alh_stream_id;
+
+ if (res->ops && res->ops->params_stream && res->dev)
+ return res->ops->params_stream(res->dev,
+ &params_data);
+ return -EIO;
+}
+
+static int intel_free_stream(struct sdw_intel *sdw,
+ int stream,
+ struct snd_soc_dai *dai,
+ int link_id)
+{
+ struct sdw_intel_link_res *res = sdw->link_res;
+ struct sdw_intel_stream_free_data free_data;
+
+ free_data.stream = stream; /* direction */
+ free_data.dai = dai;
+ free_data.link_id = link_id;
+
+ if (res->ops && res->ops->free_stream && res->dev)
+ return res->ops->free_stream(res->dev,
+ &free_data);
+
+ return 0;
+}
+
+/*
+ * bank switch routines
+ */
+
+static int intel_pre_bank_switch(struct sdw_bus *bus)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+
+ /* Write to register only for multi-link */
+ if (!bus->multi_link)
+ return 0;
+
+ intel_shim_sync_arm(sdw);
+
+ return 0;
+}
+
+static int intel_post_bank_switch(struct sdw_bus *bus)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ void __iomem *shim = sdw->link_res->shim;
+ int sync_reg, ret;
+
+ /* Write to register only for multi-link */
+ if (!bus->multi_link)
+ return 0;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ /* Read SYNC register */
+ sync_reg = intel_readl(shim, SDW_SHIM_SYNC);
+
+ /*
+ * post_bank_switch() ops is called from the bus in loop for
+ * all the Masters in the steam with the expectation that
+ * we trigger the bankswitch for the only first Master in the list
+ * and do nothing for the other Masters
+ *
+ * So, set the SYNCGO bit only if CMDSYNC bit is set for any Master.
+ */
+ if (!(sync_reg & SDW_SHIM_SYNC_CMDSYNC_MASK)) {
+ ret = 0;
+ goto unlock;
+ }
+
+ ret = intel_shim_sync_go_unlocked(sdw);
+unlock:
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ if (ret < 0)
+ dev_err(sdw->cdns.dev, "Post bank switch failed: %d\n", ret);
+
+ return ret;
+}
+
+/*
+ * DAI routines
+ */
+
+static int intel_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(cdns->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(cdns->dev,
+ "pm_runtime_resume_and_get failed in %s, ret %d\n",
+ __func__, ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int intel_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ 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_dai_runtime *dai_runtime;
+ struct sdw_cdns_pdi *pdi;
+ struct sdw_stream_config sconfig;
+ struct sdw_port_config *pconfig;
+ int ch, dir;
+ int ret;
+
+ dai_runtime = snd_soc_dai_get_dma_data(dai, substream);
+ if (!dai_runtime)
+ return -EIO;
+
+ ch = params_channels(params);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dir = SDW_DATA_DIR_RX;
+ else
+ dir = SDW_DATA_DIR_TX;
+
+ pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id);
+
+ if (!pdi) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* do run-time configurations for SHIM, ALH and PDI/PORT */
+ intel_pdi_shim_configure(sdw, pdi);
+ 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 */
+ dai_runtime->paused = false;
+ dai_runtime->suspended = false;
+ dai_runtime->pdi = pdi;
+
+ /* Inform DSP about PDI stream number */
+ ret = intel_params_stream(sdw, substream->stream, dai, params,
+ sdw->instance,
+ pdi->intel_alh_id);
+ if (ret)
+ goto error;
+
+ sconfig.direction = dir;
+ sconfig.ch_count = ch;
+ sconfig.frame_rate = params_rate(params);
+ sconfig.type = dai_runtime->stream_type;
+
+ sconfig.bps = snd_pcm_format_width(params_format(params));
+
+ /* Port configuration */
+ pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL);
+ if (!pconfig) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ pconfig->num = pdi->num;
+ pconfig->ch_mask = (1 << ch) - 1;
+
+ ret = sdw_stream_add_master(&cdns->bus, &sconfig,
+ pconfig, 1, dai_runtime->stream);
+ if (ret)
+ dev_err(cdns->dev, "add master to stream failed:%d\n", ret);
+
+ kfree(pconfig);
+error:
+ return ret;
+}
+
+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_dai_runtime *dai_runtime;
+ int ch, dir;
+ int ret = 0;
+
+ dai_runtime = snd_soc_dai_get_dma_data(dai, substream);
+ if (!dai_runtime) {
+ dev_err(dai->dev, "failed to get dai runtime in %s\n",
+ __func__);
+ return -EIO;
+ }
+
+ if (dai_runtime->suspended) {
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_pcm_hw_params *hw_params;
+
+ hw_params = &rtd->dpcm[substream->stream].hw_params;
+
+ dai_runtime->suspended = false;
+
+ /*
+ * .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
+ */
+
+ /* configure stream */
+ ch = params_channels(hw_params);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dir = SDW_DATA_DIR_RX;
+ else
+ dir = SDW_DATA_DIR_TX;
+
+ intel_pdi_shim_configure(sdw, dai_runtime->pdi);
+ intel_pdi_alh_configure(sdw, dai_runtime->pdi);
+ sdw_cdns_config_stream(cdns, ch, dir, dai_runtime->pdi);
+
+ /* Inform DSP about PDI stream number */
+ ret = intel_params_stream(sdw, substream->stream, dai,
+ hw_params,
+ sdw->instance,
+ dai_runtime->pdi->intel_alh_id);
+ }
+
+ return ret;
+}
+
+static int
+intel_hw_free(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_dai_runtime *dai_runtime;
+ int ret;
+
+ dai_runtime = snd_soc_dai_get_dma_data(dai, substream);
+ if (!dai_runtime)
+ return -EIO;
+
+ /*
+ * 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, dai_runtime->stream);
+ if (ret < 0) {
+ dev_err(dai->dev, "remove master from stream %s failed: %d\n",
+ dai_runtime->stream->name, ret);
+ return ret;
+ }
+
+ ret = intel_free_stream(sdw, substream->stream, dai, sdw->instance);
+ if (ret < 0) {
+ dev_err(dai->dev, "intel_free_stream: failed %d\n", ret);
+ return ret;
+ }
+
+ dai_runtime->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_pcm_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, int direction)
+{
+ return cdns_set_sdw_stream(dai, stream, direction);
+}
+
+static void *intel_get_sdw_stream(struct snd_soc_dai *dai,
+ int direction)
+{
+ struct sdw_cdns_dai_runtime *dai_runtime;
+
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ dai_runtime = dai->playback_dma_data;
+ else
+ dai_runtime = dai->capture_dma_data;
+
+ if (!dai_runtime)
+ return ERR_PTR(-EINVAL);
+
+ return dai_runtime->stream;
+}
+
+static int intel_trigger(struct snd_pcm_substream *substream, int cmd, 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_intel_link_res *res = sdw->link_res;
+ struct sdw_cdns_dai_runtime *dai_runtime;
+ int ret = 0;
+
+ /*
+ * The .trigger callback is used to send required IPC to audio
+ * firmware. The .free_stream callback will still be called
+ * by intel_free_stream() in the TRIGGER_SUSPEND case.
+ */
+ if (res->ops && res->ops->trigger)
+ res->ops->trigger(dai, cmd, substream->stream);
+
+ dai_runtime = snd_soc_dai_get_dma_data(dai, substream);
+ if (!dai_runtime) {
+ dev_err(dai->dev, "failed to get dai runtime in %s\n",
+ __func__);
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+
+ /*
+ * The .prepare callback is used to deal with xruns and resume operations.
+ * In the case of xruns, the DMAs and SHIM registers cannot be touched,
+ * but for resume operations the DMAs and SHIM registers need to be initialized.
+ * the .trigger callback is used to track the suspend case only.
+ */
+
+ dai_runtime->suspended = true;
+
+ ret = intel_free_stream(sdw, substream->stream, dai, sdw->instance);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dai_runtime->paused = true;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ dai_runtime->paused = false;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int intel_component_probe(struct snd_soc_component *component)
+{
+ int ret;
+
+ /*
+ * make sure the device is pm_runtime_active before initiating
+ * bus transactions during the card registration.
+ * We use pm_runtime_resume() here, without taking a reference
+ * and releasing it immediately.
+ */
+ ret = pm_runtime_resume(component->dev);
+ if (ret < 0 && ret != -EACCES)
+ return ret;
+
+ return 0;
+}
+
+static int intel_component_dais_suspend(struct snd_soc_component *component)
+{
+ struct snd_soc_dai *dai;
+
+ /*
+ * In the corner case where a SUSPEND happens during a PAUSE, the ALSA core
+ * does not throw the TRIGGER_SUSPEND. This leaves the DAIs in an unbalanced state.
+ * Since the component suspend is called last, we can trap this corner case
+ * and force the DAIs to release their resources.
+ */
+ for_each_component_dais(component, dai) {
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_cdns_dai_runtime *dai_runtime;
+ int stream;
+ int ret;
+
+ dai_runtime = dai->playback_dma_data;
+ stream = SNDRV_PCM_STREAM_PLAYBACK;
+ if (!dai_runtime) {
+ dai_runtime = dai->capture_dma_data;
+ stream = SNDRV_PCM_STREAM_CAPTURE;
+ }
+
+ if (!dai_runtime)
+ continue;
+
+ if (dai_runtime->suspended)
+ continue;
+
+ if (dai_runtime->paused) {
+ dai_runtime->suspended = true;
+
+ ret = intel_free_stream(sdw, stream, dai, sdw->instance);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops intel_pcm_dai_ops = {
+ .startup = intel_startup,
+ .hw_params = intel_hw_params,
+ .prepare = intel_prepare,
+ .hw_free = intel_hw_free,
+ .trigger = intel_trigger,
+ .shutdown = intel_shutdown,
+ .set_stream = intel_pcm_set_sdw_stream,
+ .get_stream = intel_get_sdw_stream,
+};
+
+static const struct snd_soc_component_driver dai_component = {
+ .name = "soundwire",
+ .probe = intel_component_probe,
+ .suspend = intel_component_dais_suspend,
+ .legacy_dai_naming = 1,
+};
+
+static int intel_create_dai(struct sdw_cdns *cdns,
+ struct snd_soc_dai_driver *dais,
+ enum intel_pdi_type type,
+ u32 num, u32 off, u32 max_ch)
+{
+ int i;
+
+ if (num == 0)
+ return 0;
+
+ /* TODO: Read supported rates/formats from hardware */
+ for (i = off; i < (off + num); i++) {
+ dais[i].name = devm_kasprintf(cdns->dev, GFP_KERNEL,
+ "SDW%d Pin%d",
+ cdns->instance, i);
+ if (!dais[i].name)
+ return -ENOMEM;
+
+ if (type == INTEL_PDI_BD || type == INTEL_PDI_OUT) {
+ dais[i].playback.channels_min = 1;
+ dais[i].playback.channels_max = max_ch;
+ dais[i].playback.rates = SNDRV_PCM_RATE_48000;
+ dais[i].playback.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ }
+
+ if (type == INTEL_PDI_BD || type == INTEL_PDI_IN) {
+ dais[i].capture.channels_min = 1;
+ dais[i].capture.channels_max = max_ch;
+ dais[i].capture.rates = SNDRV_PCM_RATE_48000;
+ dais[i].capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ }
+
+ dais[i].ops = &intel_pcm_dai_ops;
+ }
+
+ return 0;
+}
+
+static int intel_register_dai(struct sdw_intel *sdw)
+{
+ struct sdw_cdns_stream_config config;
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_cdns_streams *stream;
+ struct snd_soc_dai_driver *dais;
+ int num_dai, ret, off = 0;
+
+ /* Read the PDI config and initialize cadence PDI */
+ intel_pdi_init(sdw, &config);
+ ret = sdw_cdns_pdi_init(cdns, config);
+ if (ret)
+ return ret;
+
+ intel_pdi_ch_update(sdw);
+
+ /* DAIs are created based on total number of PDIs supported */
+ num_dai = cdns->pcm.num_pdi;
+
+ dais = devm_kcalloc(cdns->dev, num_dai, sizeof(*dais), GFP_KERNEL);
+ if (!dais)
+ return -ENOMEM;
+
+ /* Create PCM DAIs */
+ stream = &cdns->pcm;
+
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_IN, cdns->pcm.num_in,
+ off, stream->num_ch_in);
+ if (ret)
+ return ret;
+
+ off += cdns->pcm.num_in;
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT, cdns->pcm.num_out,
+ off, stream->num_ch_out);
+ if (ret)
+ return ret;
+
+ off += cdns->pcm.num_out;
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_BD, cdns->pcm.num_bd,
+ off, stream->num_ch_bd);
+ if (ret)
+ return ret;
+
+ return devm_snd_soc_register_component(cdns->dev, &dai_component,
+ dais, num_dai);
+}
+
+static int intel_start_bus(struct sdw_intel *sdw)
+{
+ struct device *dev = sdw->cdns.dev;
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_bus *bus = &cdns->bus;
+ int ret;
+
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret);
+ return ret;
+ }
+
+ /*
+ * follow recommended programming flows to avoid timeouts when
+ * gsync is enabled
+ */
+ if (bus->multi_link)
+ intel_shim_sync_arm(sdw);
+
+ ret = sdw_cdns_init(cdns);
+ if (ret < 0) {
+ dev_err(dev, "%s: unable to initialize Cadence IP: %d\n", __func__, ret);
+ goto err_interrupt;
+ }
+
+ ret = sdw_cdns_exit_reset(cdns);
+ if (ret < 0) {
+ dev_err(dev, "%s: unable to exit bus reset sequence: %d\n", __func__, ret);
+ goto err_interrupt;
+ }
+
+ if (bus->multi_link) {
+ ret = intel_shim_sync_go(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: sync go failed: %d\n", __func__, ret);
+ goto err_interrupt;
+ }
+ }
+ sdw_cdns_check_self_clearing_bits(cdns, __func__,
+ true, INTEL_MASTER_RESET_ITERATIONS);
+
+ return 0;
+
+err_interrupt:
+ sdw_cdns_enable_interrupt(cdns, false);
+ return ret;
+}
+
+static int intel_start_bus_after_reset(struct sdw_intel *sdw)
+{
+ struct device *dev = sdw->cdns.dev;
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_bus *bus = &cdns->bus;
+ bool clock_stop0;
+ int status;
+ int 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 (bus->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");
+ goto err_interrupt;
+ }
+
+ if (!clock_stop0) {
+ ret = sdw_cdns_exit_reset(cdns);
+ if (ret < 0) {
+ dev_err(dev, "unable to exit bus reset sequence during resume\n");
+ goto err_interrupt;
+ }
+
+ if (bus->multi_link) {
+ ret = intel_shim_sync_go(sdw);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "sync go failed during resume\n");
+ goto err_interrupt;
+ }
+ }
+ }
+ sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS);
+
+ return 0;
+
+err_interrupt:
+ sdw_cdns_enable_interrupt(cdns, false);
+ return ret;
+}
+
+static void intel_check_clock_stop(struct sdw_intel *sdw)
+{
+ struct device *dev = sdw->cdns.dev;
+ bool clock_stop0;
+
+ clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
+ if (!clock_stop0)
+ dev_err(dev, "%s: invalid configuration, clock was not stopped\n", __func__);
+}
+
+static int intel_start_bus_after_clock_stop(struct sdw_intel *sdw)
+{
+ struct device *dev = sdw->cdns.dev;
+ struct sdw_cdns *cdns = &sdw->cdns;
+ int ret;
+
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = sdw_cdns_clock_restart(cdns, false);
+ if (ret < 0) {
+ dev_err(dev, "%s: unable to restart clock: %d\n", __func__, ret);
+ sdw_cdns_enable_interrupt(cdns, false);
+ return ret;
+ }
+
+ sdw_cdns_check_self_clearing_bits(cdns, "intel_resume_runtime no_quirks",
+ true, INTEL_MASTER_RESET_ITERATIONS);
+
+ return 0;
+}
+
+static int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
+{
+ struct device *dev = sdw->cdns.dev;
+ struct sdw_cdns *cdns = &sdw->cdns;
+ bool wake_enable = false;
+ int ret;
+
+ if (clock_stop) {
+ ret = sdw_cdns_clock_stop(cdns, true);
+ if (ret < 0)
+ dev_err(dev, "%s: cannot stop clock: %d\n", __func__, ret);
+ else
+ wake_enable = true;
+ }
+
+ ret = sdw_cdns_enable_interrupt(cdns, false);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot disable interrupts: %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = intel_link_power_down(sdw);
+ if (ret) {
+ dev_err(dev, "%s: Link power down failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ intel_shim_wake(sdw, wake_enable);
+
+ return 0;
+}
+
+static int sdw_master_read_intel_prop(struct sdw_bus *bus)
+{
+ struct sdw_master_prop *prop = &bus->prop;
+ struct fwnode_handle *link;
+ char name[32];
+ u32 quirk_mask;
+
+ /* Find master handle */
+ snprintf(name, sizeof(name),
+ "mipi-sdw-link-%d-subproperties", bus->link_id);
+
+ link = device_get_named_child_node(bus->dev, name);
+ if (!link) {
+ dev_err(bus->dev, "Master node %s not found\n", name);
+ return -EIO;
+ }
+
+ fwnode_property_read_u32(link,
+ "intel-sdw-ip-clock",
+ &prop->mclk_freq);
+
+ /* the values reported by BIOS are the 2x clock, not the bus clock */
+ prop->mclk_freq /= 2;
+
+ fwnode_property_read_u32(link,
+ "intel-quirk-mask",
+ &quirk_mask);
+
+ if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE)
+ prop->hw_disabled = true;
+
+ prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
+ SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
+
+ return 0;
+}
+
+static int intel_prop_read(struct sdw_bus *bus)
+{
+ /* Initialize with default handler to read all DisCo properties */
+ sdw_master_read_prop(bus);
+
+ /* read Intel-specific properties */
+ sdw_master_read_intel_prop(bus);
+
+ return 0;
+}
+
+static struct sdw_master_ops sdw_intel_ops = {
+ .read_prop = intel_prop_read,
+ .override_adr = sdw_dmi_override_adr,
+ .xfer_msg = cdns_xfer_msg,
+ .xfer_msg_defer = cdns_xfer_msg_defer,
+ .reset_page_addr = cdns_reset_page_addr,
+ .set_bus_conf = cdns_bus_conf,
+ .pre_bank_switch = intel_pre_bank_switch,
+ .post_bank_switch = intel_post_bank_switch,
+ .read_ping_status = cdns_read_ping_status,
+};
+
+/*
+ * probe and init (aux_dev_id argument is required by function prototype but not used)
+ */
+static int intel_link_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *aux_dev_id)
+
+{
+ struct device *dev = &auxdev->dev;
+ struct sdw_intel_link_dev *ldev = auxiliary_dev_to_sdw_intel_link_dev(auxdev);
+ struct sdw_intel *sdw;
+ struct sdw_cdns *cdns;
+ struct sdw_bus *bus;
+ int ret;
+
+ sdw = devm_kzalloc(dev, sizeof(*sdw), GFP_KERNEL);
+ if (!sdw)
+ return -ENOMEM;
+
+ cdns = &sdw->cdns;
+ bus = &cdns->bus;
+
+ sdw->instance = auxdev->id;
+ sdw->link_res = &ldev->link_res;
+ cdns->dev = dev;
+ cdns->registers = sdw->link_res->registers;
+ cdns->instance = sdw->instance;
+ cdns->msg_count = 0;
+
+ bus->link_id = auxdev->id;
+ bus->dev_num_ida_min = INTEL_DEV_NUM_IDA_MIN;
+ bus->clk_stop_timeout = 1;
+
+ sdw_cdns_probe(cdns);
+
+ /* Set ops */
+ bus->ops = &sdw_intel_ops;
+
+ /* set driver data, accessed by snd_soc_dai_get_drvdata() */
+ auxiliary_set_drvdata(auxdev, cdns);
+
+ /* use generic bandwidth allocation algorithm */
+ sdw->cdns.bus.compute_params = sdw_compute_params;
+
+ /* avoid resuming from pm_runtime suspend if it's not required */
+ dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND);
+
+ ret = sdw_bus_master_add(bus, dev, dev->fwnode);
+ if (ret) {
+ dev_err(dev, "sdw_bus_master_add fail: %d\n", ret);
+ return ret;
+ }
+
+ if (bus->prop.hw_disabled)
+ 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;
+}
+
+int intel_link_startup(struct auxiliary_device *auxdev)
+{
+ struct device *dev = &auxdev->dev;
+ struct sdw_cdns *cdns = auxiliary_get_drvdata(auxdev);
+ 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) {
+ dev_info(dev,
+ "SoundWire master %d is disabled, ignoring\n",
+ sdw->instance);
+ return 0;
+ }
+
+ 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");
+ } 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->hw_sync_min_links = 1;
+ }
+ bus->multi_link = multi_link;
+
+ /* Initialize shim, controller */
+ ret = intel_link_power_up(sdw);
+ if (ret)
+ goto err_init;
+
+ /* Register DAIs */
+ ret = intel_register_dai(sdw);
+ if (ret) {
+ dev_err(dev, "DAI registration failed: %d\n", ret);
+ goto err_power_up;
+ }
+
+ intel_debugfs_init(sdw);
+
+ /* start bus */
+ ret = intel_start_bus(sdw);
+ if (ret) {
+ dev_err(dev, "bus start failed: %d\n", ret);
+ goto err_power_up;
+ }
+
+ /* 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);
+
+ sdw->startup_done = true;
+ return 0;
+
+err_power_up:
+ intel_link_power_down(sdw);
+err_init:
+ return ret;
+}
+
+static void intel_link_remove(struct auxiliary_device *auxdev)
+{
+ struct sdw_cdns *cdns = auxiliary_get_drvdata(auxdev);
+ 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);
+ }
+ sdw_bus_master_delete(bus);
+}
+
+int intel_link_process_wakeen_event(struct auxiliary_device *auxdev)
+{
+ struct device *dev = &auxdev->dev;
+ struct sdw_intel *sdw;
+ struct sdw_bus *bus;
+
+ sdw = auxiliary_get_drvdata(auxdev);
+ bus = &sdw->cdns.bus;
+
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ if (!intel_shim_check_wake(sdw))
+ return 0;
+
+ /* disable WAKEEN interrupt ASAP to prevent interrupt flood */
+ intel_shim_wake(sdw, false);
+
+ /*
+ * resume the Master, which will generate a bus reset and result in
+ * Slaves re-attaching and be re-enumerated. The SoundWire physical
+ * device which generated the wake will trigger an interrupt, which
+ * will in turn cause the corresponding Linux Slave device to be
+ * resumed and the Slave codec driver to check the status.
+ */
+ pm_request_resume(dev);
+
+ return 0;
+}
+
+/*
+ * PM calls
+ */
+
+static int intel_resume_child_device(struct device *dev, void *data)
+{
+ int ret;
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+
+ if (!slave->probed) {
+ dev_dbg(dev, "skipping device, no probed driver\n");
+ return 0;
+ }
+ if (!slave->dev_num_sticky) {
+ dev_dbg(dev, "skipping device, never detected on bus\n");
+ return 0;
+ }
+
+ ret = pm_request_resume(dev);
+ if (ret < 0)
+ dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int __maybe_unused intel_pm_prepare(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 || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+
+ if (pm_runtime_suspended(dev) &&
+ pm_runtime_suspended(dev->parent) &&
+ ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
+ !clock_stop_quirks)) {
+ /*
+ * if we've enabled clock stop, and the parent is suspended, the SHIM registers
+ * are not accessible and the shim wake cannot be disabled.
+ * The only solution is to resume the entire bus to full power
+ */
+
+ /*
+ * If any operation in this block fails, we keep going since we don't want
+ * to prevent system suspend from happening and errors should be recoverable
+ * on resume.
+ */
+
+ /*
+ * first resume the device for this link. This will also by construction
+ * resume the PCI parent device.
+ */
+ ret = pm_request_resume(dev);
+ if (ret < 0) {
+ dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
+ return 0;
+ }
+
+ /*
+ * Continue resuming the entire bus (parent + child devices) to exit
+ * the clock stop mode. If there are no devices connected on this link
+ * this is a no-op.
+ * The resume to full power could have been implemented with a .prepare
+ * step in SoundWire codec drivers. This would however require a lot
+ * of code to handle an Intel-specific corner case. It is simpler in
+ * practice to add a loop at the link level.
+ */
+ ret = device_for_each_child(bus->dev, NULL, intel_resume_child_device);
+
+ if (ret < 0)
+ dev_err(dev, "%s: intel_resume_child_device failed: %d\n", __func__, ret);
+ }
+
+ return 0;
+}
+
+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 || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ if (pm_runtime_suspended(dev)) {
+ dev_dbg(dev, "pm_runtime status: suspended\n");
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+
+ if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
+ !clock_stop_quirks) {
+
+ if (pm_runtime_suspended(dev->parent)) {
+ /*
+ * paranoia check: this should not happen with the .prepare
+ * resume to full power
+ */
+ dev_err(dev, "%s: invalid config: parent is suspended\n", __func__);
+ } else {
+ intel_shim_wake(sdw, false);
+ }
+ }
+
+ return 0;
+ }
+
+ ret = intel_stop_bus(sdw, false);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot stop bus: %d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused 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 || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, 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 = intel_stop_bus(sdw, false);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot stop bus during teardown: %d\n",
+ __func__, ret);
+ return ret;
+ }
+ } else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET || !clock_stop_quirks) {
+ ret = intel_stop_bus(sdw, true);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot stop bus during clock_stop: %d\n",
+ __func__, ret);
+ return ret;
+ }
+ } 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;
+ int ret;
+
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ link_flags = md_flags >> (bus->link_id * 8);
+
+ if (pm_runtime_suspended(dev)) {
+ dev_dbg(dev, "pm_runtime status was suspended, forcing active\n");
+
+ /* 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_link_power_up(sdw);
+ if (ret) {
+ dev_err(dev, "%s failed: %d\n", __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 = intel_start_bus(sdw);
+ if (ret < 0) {
+ dev_err(dev, "cannot start bus during resume\n");
+ intel_link_power_down(sdw);
+ 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 0;
+}
+
+static int __maybe_unused 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;
+ int ret;
+
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ /* unconditionally disable WAKEEN interrupt */
+ intel_shim_wake(sdw, false);
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+
+ if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
+ ret = intel_link_power_up(sdw);
+ if (ret) {
+ dev_err(dev, "%s: power_up failed after teardown: %d\n", __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 = intel_start_bus(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot start bus after teardown: %d\n", __func__, ret);
+ intel_link_power_down(sdw);
+ return ret;
+ }
+
+
+ } else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) {
+ ret = intel_link_power_up(sdw);
+ if (ret) {
+ dev_err(dev, "%s: power_up failed after bus reset: %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = intel_start_bus_after_reset(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot start bus after reset: %d\n", __func__, ret);
+ intel_link_power_down(sdw);
+ return ret;
+ }
+ } else if (!clock_stop_quirks) {
+
+ intel_check_clock_stop(sdw);
+
+ ret = intel_link_power_up(sdw);
+ if (ret) {
+ dev_err(dev, "%s: power_up failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = intel_start_bus_after_clock_stop(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot start bus after clock stop: %d\n", __func__, ret);
+ intel_link_power_down(sdw);
+ return ret;
+ }
+ } else {
+ dev_err(dev, "%s: clock_stop_quirks %x unsupported\n",
+ __func__, clock_stop_quirks);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct dev_pm_ops intel_pm = {
+ .prepare = intel_pm_prepare,
+ SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume)
+ SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL)
+};
+
+static const struct auxiliary_device_id intel_link_id_table[] = {
+ { .name = "soundwire_intel.link" },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, intel_link_id_table);
+
+static struct auxiliary_driver sdw_intel_drv = {
+ .probe = intel_link_probe,
+ .remove = intel_link_remove,
+ .driver = {
+ /* auxiliary_driver_register() sets .name to be the modname */
+ .pm = &intel_pm,
+ },
+ .id_table = intel_link_id_table
+};
+module_auxiliary_driver(sdw_intel_drv);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Intel Soundwire Link Driver");
diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h
new file mode 100644
index 000000000..cd93a44db
--- /dev/null
+++ b/drivers/soundwire/intel.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* Copyright(c) 2015-17 Intel Corporation. */
+
+#ifndef __SDW_INTEL_LOCAL_H
+#define __SDW_INTEL_LOCAL_H
+
+/**
+ * struct sdw_intel_link_res - Soundwire Intel link resource structure,
+ * typically populated by the controller driver.
+ * @mmio_base: mmio base of SoundWire registers
+ * @registers: Link IO registers base
+ * @shim: Audio shim pointer
+ * @alh: ALH (Audio Link Hub) pointer
+ * @irq: Interrupt line
+ * @ops: Shim callback ops
+ * @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
+ */
+struct sdw_intel_link_res {
+ void __iomem *mmio_base; /* not strictly needed, useful for debug */
+ void __iomem *registers;
+ void __iomem *shim;
+ void __iomem *alh;
+ int irq;
+ const struct sdw_intel_ops *ops;
+ 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;
+};
+
+struct sdw_intel {
+ struct sdw_cdns cdns;
+ int instance;
+ struct sdw_intel_link_res *link_res;
+ bool startup_done;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs;
+#endif
+};
+
+int intel_link_startup(struct auxiliary_device *auxdev);
+int intel_link_process_wakeen_event(struct auxiliary_device *auxdev);
+
+struct sdw_intel_link_dev {
+ struct auxiliary_device auxdev;
+ struct sdw_intel_link_res link_res;
+};
+
+#define auxiliary_dev_to_sdw_intel_link_dev(auxiliary_dev) \
+ container_of(auxiliary_dev, struct sdw_intel_link_dev, auxdev)
+
+#endif /* __SDW_INTEL_LOCAL_H */
diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c
new file mode 100644
index 000000000..d09151391
--- /dev/null
+++ b/drivers/soundwire/intel_init.c
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+/*
+ * SDW Intel Init Routines
+ *
+ * Initializes and creates SDW devices based on ACPI and Hardware values
+ */
+
+#include <linux/acpi.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw_intel.h>
+#include "cadence_master.h"
+#include "intel.h"
+
+static void intel_link_dev_release(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct sdw_intel_link_dev *ldev = auxiliary_dev_to_sdw_intel_link_dev(auxdev);
+
+ kfree(ldev);
+}
+
+/* alloc, init and add link devices */
+static struct sdw_intel_link_dev *intel_link_dev_register(struct sdw_intel_res *res,
+ struct sdw_intel_ctx *ctx,
+ struct fwnode_handle *fwnode,
+ const char *name,
+ int link_id)
+{
+ struct sdw_intel_link_dev *ldev;
+ struct sdw_intel_link_res *link;
+ struct auxiliary_device *auxdev;
+ int ret;
+
+ ldev = kzalloc(sizeof(*ldev), GFP_KERNEL);
+ if (!ldev)
+ return ERR_PTR(-ENOMEM);
+
+ auxdev = &ldev->auxdev;
+ auxdev->name = name;
+ auxdev->dev.parent = res->parent;
+ auxdev->dev.fwnode = fwnode;
+ auxdev->dev.release = intel_link_dev_release;
+
+ /* we don't use an IDA since we already have a link ID */
+ auxdev->id = link_id;
+
+ /*
+ * keep a handle on the allocated memory, to be used in all other functions.
+ * Since the same pattern is used to skip links that are not enabled, there is
+ * no need to check if ctx->ldev[i] is NULL later on.
+ */
+ ctx->ldev[link_id] = ldev;
+
+ /* Add link information used in the driver probe */
+ link = &ldev->link_res;
+ link->mmio_base = res->mmio_base;
+ link->registers = res->mmio_base + SDW_LINK_BASE
+ + (SDW_LINK_SIZE * link_id);
+ link->shim = res->mmio_base + res->shim_base;
+ link->alh = res->mmio_base + res->alh_base;
+
+ 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 = ctx->link_mask;
+
+ /* now follow the two-step init/add sequence */
+ ret = auxiliary_device_init(auxdev);
+ if (ret < 0) {
+ dev_err(res->parent, "failed to initialize link dev %s link_id %d\n",
+ name, link_id);
+ kfree(ldev);
+ return ERR_PTR(ret);
+ }
+
+ ret = auxiliary_device_add(&ldev->auxdev);
+ if (ret < 0) {
+ dev_err(res->parent, "failed to add link dev %s link_id %d\n",
+ ldev->auxdev.name, link_id);
+ /* ldev will be freed with the put_device() and .release sequence */
+ auxiliary_device_uninit(&ldev->auxdev);
+ return ERR_PTR(ret);
+ }
+
+ return ldev;
+}
+
+static void intel_link_dev_unregister(struct sdw_intel_link_dev *ldev)
+{
+ auxiliary_device_delete(&ldev->auxdev);
+ auxiliary_device_uninit(&ldev->auxdev);
+}
+
+static int sdw_intel_cleanup(struct sdw_intel_ctx *ctx)
+{
+ struct sdw_intel_link_dev *ldev;
+ u32 link_mask;
+ int i;
+
+ link_mask = ctx->link_mask;
+
+ for (i = 0; i < ctx->count; i++) {
+ if (!(link_mask & BIT(i)))
+ continue;
+
+ ldev = ctx->ldev[i];
+
+ pm_runtime_disable(&ldev->auxdev.dev);
+ if (!ldev->link_res.clock_stop_quirks)
+ pm_runtime_put_noidle(ldev->link_res.dev);
+
+ intel_link_dev_unregister(ldev);
+ }
+
+ return 0;
+}
+
+#define HDA_DSP_REG_ADSPIC2 (0x10)
+#define HDA_DSP_REG_ADSPIS2 (0x14)
+#define HDA_DSP_REG_ADSPIC2_SNDW BIT(5)
+
+/**
+ * sdw_intel_enable_irq() - enable/disable Intel SoundWire IRQ
+ * @mmio_base: The mmio base of the control register
+ * @enable: true if enable
+ */
+void sdw_intel_enable_irq(void __iomem *mmio_base, bool enable)
+{
+ u32 val;
+
+ val = readl(mmio_base + HDA_DSP_REG_ADSPIC2);
+
+ if (enable)
+ val |= HDA_DSP_REG_ADSPIC2_SNDW;
+ else
+ val &= ~HDA_DSP_REG_ADSPIC2_SNDW;
+
+ writel(val, mmio_base + HDA_DSP_REG_ADSPIC2);
+}
+EXPORT_SYMBOL_NS(sdw_intel_enable_irq, SOUNDWIRE_INTEL_INIT);
+
+irqreturn_t sdw_intel_thread(int irq, void *dev_id)
+{
+ struct sdw_intel_ctx *ctx = dev_id;
+ struct sdw_intel_link_res *link;
+
+ list_for_each_entry(link, &ctx->link_list, list)
+ sdw_cdns_irq(irq, link->cdns);
+
+ sdw_intel_enable_irq(ctx->mmio_base, true);
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_NS(sdw_intel_thread, SOUNDWIRE_INTEL_INIT);
+
+static struct sdw_intel_ctx
+*sdw_intel_probe_controller(struct sdw_intel_res *res)
+{
+ struct sdw_intel_link_res *link;
+ struct sdw_intel_link_dev *ldev;
+ struct sdw_intel_ctx *ctx;
+ struct acpi_device *adev;
+ struct sdw_slave *slave;
+ struct list_head *node;
+ struct sdw_bus *bus;
+ u32 link_mask;
+ int num_slaves = 0;
+ int count;
+ int i;
+
+ if (!res)
+ return NULL;
+
+ adev = acpi_fetch_acpi_dev(res->handle);
+ if (!adev)
+ return NULL;
+
+ if (!res->count)
+ return NULL;
+
+ count = res->count;
+ dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count);
+
+ /*
+ * we need to alloc/free memory manually and can't use devm:
+ * this routine may be called from a workqueue, and not from
+ * the parent .probe.
+ * If devm_ was used, the memory might never be freed on errors.
+ */
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return NULL;
+
+ ctx->count = count;
+
+ /*
+ * allocate the array of pointers. The link-specific data is allocated
+ * as part of the first loop below and released with the auxiliary_device_uninit().
+ * If some links are disabled, the link pointer will remain NULL. Given that the
+ * number of links is small, this is simpler than using a list to keep track of links.
+ */
+ ctx->ldev = kcalloc(ctx->count, sizeof(*ctx->ldev), GFP_KERNEL);
+ if (!ctx->ldev) {
+ kfree(ctx);
+ return NULL;
+ }
+
+ ctx->mmio_base = res->mmio_base;
+ ctx->shim_base = res->shim_base;
+ ctx->alh_base = res->alh_base;
+ ctx->link_mask = res->link_mask;
+ ctx->handle = res->handle;
+ mutex_init(&ctx->shim_lock);
+
+ link_mask = ctx->link_mask;
+
+ INIT_LIST_HEAD(&ctx->link_list);
+
+ for (i = 0; i < count; i++) {
+ if (!(link_mask & BIT(i)))
+ continue;
+
+ /*
+ * init and add a device for each link
+ *
+ * The name of the device will be soundwire_intel.link.[i],
+ * with the "soundwire_intel" module prefix automatically added
+ * by the auxiliary bus core.
+ */
+ ldev = intel_link_dev_register(res,
+ ctx,
+ acpi_fwnode_handle(adev),
+ "link",
+ i);
+ if (IS_ERR(ldev))
+ goto err;
+
+ link = &ldev->link_res;
+ link->cdns = auxiliary_get_drvdata(&ldev->auxdev);
+
+ if (!link->cdns) {
+ dev_err(&adev->dev, "failed to get link->cdns\n");
+ /*
+ * 1 will be subtracted from i in the err label, but we need to call
+ * intel_link_dev_unregister for this ldev, so plus 1 now
+ */
+ i++;
+ goto err;
+ }
+ list_add_tail(&link->list, &ctx->link_list);
+ bus = &link->cdns->bus;
+ /* Calculate number of slaves */
+ list_for_each(node, &bus->slaves)
+ num_slaves++;
+ }
+
+ ctx->ids = kcalloc(num_slaves, sizeof(*ctx->ids), GFP_KERNEL);
+ if (!ctx->ids)
+ goto err;
+
+ ctx->num_slaves = num_slaves;
+ i = 0;
+ list_for_each_entry(link, &ctx->link_list, list) {
+ bus = &link->cdns->bus;
+ list_for_each_entry(slave, &bus->slaves, node) {
+ ctx->ids[i].id = slave->id;
+ ctx->ids[i].link_id = bus->link_id;
+ i++;
+ }
+ }
+
+ return ctx;
+
+err:
+ while (i--) {
+ if (!(link_mask & BIT(i)))
+ continue;
+ ldev = ctx->ldev[i];
+ intel_link_dev_unregister(ldev);
+ }
+ kfree(ctx->ldev);
+ kfree(ctx);
+ return NULL;
+}
+
+static int
+sdw_intel_startup_controller(struct sdw_intel_ctx *ctx)
+{
+ struct acpi_device *adev = acpi_fetch_acpi_dev(ctx->handle);
+ struct sdw_intel_link_dev *ldev;
+ u32 caps;
+ u32 link_mask;
+ int i;
+
+ if (!adev)
+ return -EINVAL;
+
+ /* Check SNDWLCAP.LCOUNT */
+ caps = ioread32(ctx->mmio_base + ctx->shim_base + SDW_SHIM_LCAP);
+ caps &= SDW_SHIM_LCAP_LCOUNT_MASK;
+
+ /* Check HW supported vs property value */
+ if (caps < ctx->count) {
+ dev_err(&adev->dev,
+ "BIOS master count is larger than hardware capabilities\n");
+ return -EINVAL;
+ }
+
+ if (!ctx->ldev)
+ return -EINVAL;
+
+ link_mask = ctx->link_mask;
+
+ /* Startup SDW Master devices */
+ for (i = 0; i < ctx->count; i++) {
+ if (!(link_mask & BIT(i)))
+ continue;
+
+ ldev = ctx->ldev[i];
+
+ intel_link_startup(&ldev->auxdev);
+
+ if (!ldev->link_res.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(ldev->link_res.dev);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_intel_probe() - SoundWire Intel probe routine
+ * @res: resource data
+ *
+ * This registers an auxiliary device for each Master handled by the controller,
+ * and SoundWire Master and Slave devices will be created by the auxiliary
+ * device probe. All the information necessary is stored in the context, and
+ * the res argument pointer can be freed after this step.
+ * This function will be called after sdw_intel_acpi_scan() by SOF probe.
+ */
+struct sdw_intel_ctx
+*sdw_intel_probe(struct sdw_intel_res *res)
+{
+ return sdw_intel_probe_controller(res);
+}
+EXPORT_SYMBOL_NS(sdw_intel_probe, SOUNDWIRE_INTEL_INIT);
+
+/**
+ * sdw_intel_startup() - SoundWire Intel startup
+ * @ctx: SoundWire context allocated in the probe
+ *
+ * Startup Intel SoundWire controller. This function will be called after
+ * Intel Audio DSP is powered up.
+ */
+int sdw_intel_startup(struct sdw_intel_ctx *ctx)
+{
+ return sdw_intel_startup_controller(ctx);
+}
+EXPORT_SYMBOL_NS(sdw_intel_startup, SOUNDWIRE_INTEL_INIT);
+/**
+ * sdw_intel_exit() - SoundWire Intel exit
+ * @ctx: SoundWire context allocated in the probe
+ *
+ * Delete the controller instances created and cleanup
+ */
+void sdw_intel_exit(struct sdw_intel_ctx *ctx)
+{
+ sdw_intel_cleanup(ctx);
+ kfree(ctx->ids);
+ kfree(ctx->ldev);
+ kfree(ctx);
+}
+EXPORT_SYMBOL_NS(sdw_intel_exit, SOUNDWIRE_INTEL_INIT);
+
+void sdw_intel_process_wakeen_event(struct sdw_intel_ctx *ctx)
+{
+ struct sdw_intel_link_dev *ldev;
+ u32 link_mask;
+ int i;
+
+ if (!ctx->ldev)
+ return;
+
+ link_mask = ctx->link_mask;
+
+ /* Startup SDW Master devices */
+ for (i = 0; i < ctx->count; i++) {
+ if (!(link_mask & BIT(i)))
+ continue;
+
+ ldev = ctx->ldev[i];
+
+ intel_link_process_wakeen_event(&ldev->auxdev);
+ }
+}
+EXPORT_SYMBOL_NS(sdw_intel_process_wakeen_event, SOUNDWIRE_INTEL_INIT);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Intel Soundwire Init Library");
diff --git a/drivers/soundwire/master.c b/drivers/soundwire/master.c
new file mode 100644
index 000000000..9b05c9e25
--- /dev/null
+++ b/drivers/soundwire/master.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2019-2020 Intel Corporation.
+
+#include <linux/device.h>
+#include <linux/acpi.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+
+/*
+ * The 3s value for autosuspend will only be used if there are no
+ * devices physically attached on a bus segment. In practice enabling
+ * the bus operation will result in children devices become active and
+ * the master device will only suspend when all its children are no
+ * longer active.
+ */
+#define SDW_MASTER_SUSPEND_DELAY_MS 3000
+
+/*
+ * The sysfs for properties reflects the MIPI description as given
+ * in the MIPI DisCo spec
+ *
+ * Base file is:
+ * sdw-master-N
+ * |---- revision
+ * |---- clk_stop_modes
+ * |---- max_clk_freq
+ * |---- clk_freq
+ * |---- clk_gears
+ * |---- default_row
+ * |---- default_col
+ * |---- dynamic_shape
+ * |---- err_threshold
+ */
+
+#define sdw_master_attr(field, format_string) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_master_device *md = dev_to_sdw_master_device(dev); \
+ return sprintf(buf, format_string, md->bus->prop.field); \
+} \
+static DEVICE_ATTR_RO(field)
+
+sdw_master_attr(revision, "0x%x\n");
+sdw_master_attr(clk_stop_modes, "0x%x\n");
+sdw_master_attr(max_clk_freq, "%d\n");
+sdw_master_attr(default_row, "%d\n");
+sdw_master_attr(default_col, "%d\n");
+sdw_master_attr(default_frame_rate, "%d\n");
+sdw_master_attr(dynamic_frame, "%d\n");
+sdw_master_attr(err_threshold, "%d\n");
+
+static ssize_t clock_frequencies_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_master_device *md = dev_to_sdw_master_device(dev);
+ ssize_t size = 0;
+ int i;
+
+ for (i = 0; i < md->bus->prop.num_clk_freq; i++)
+ size += sprintf(buf + size, "%8d ",
+ md->bus->prop.clk_freq[i]);
+ size += sprintf(buf + size, "\n");
+
+ return size;
+}
+static DEVICE_ATTR_RO(clock_frequencies);
+
+static ssize_t clock_gears_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_master_device *md = dev_to_sdw_master_device(dev);
+ ssize_t size = 0;
+ int i;
+
+ for (i = 0; i < md->bus->prop.num_clk_gears; i++)
+ size += sprintf(buf + size, "%8d ",
+ md->bus->prop.clk_gears[i]);
+ size += sprintf(buf + size, "\n");
+
+ return size;
+}
+static DEVICE_ATTR_RO(clock_gears);
+
+static struct attribute *master_node_attrs[] = {
+ &dev_attr_revision.attr,
+ &dev_attr_clk_stop_modes.attr,
+ &dev_attr_max_clk_freq.attr,
+ &dev_attr_default_row.attr,
+ &dev_attr_default_col.attr,
+ &dev_attr_default_frame_rate.attr,
+ &dev_attr_dynamic_frame.attr,
+ &dev_attr_err_threshold.attr,
+ &dev_attr_clock_frequencies.attr,
+ &dev_attr_clock_gears.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(master_node);
+
+static void sdw_master_device_release(struct device *dev)
+{
+ struct sdw_master_device *md = dev_to_sdw_master_device(dev);
+
+ kfree(md);
+}
+
+static const struct dev_pm_ops master_dev_pm = {
+ SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend,
+ pm_generic_runtime_resume, NULL)
+};
+
+struct device_type sdw_master_type = {
+ .name = "soundwire_master",
+ .release = sdw_master_device_release,
+ .pm = &master_dev_pm,
+};
+
+/**
+ * sdw_master_device_add() - create a Linux Master Device representation.
+ * @bus: SDW bus instance
+ * @parent: parent device
+ * @fwnode: firmware node handle
+ */
+int sdw_master_device_add(struct sdw_bus *bus, struct device *parent,
+ struct fwnode_handle *fwnode)
+{
+ struct sdw_master_device *md;
+ int ret;
+
+ if (!parent)
+ return -EINVAL;
+
+ md = kzalloc(sizeof(*md), GFP_KERNEL);
+ if (!md)
+ return -ENOMEM;
+
+ md->dev.bus = &sdw_bus_type;
+ md->dev.type = &sdw_master_type;
+ md->dev.parent = parent;
+ md->dev.groups = master_node_groups;
+ md->dev.of_node = parent->of_node;
+ md->dev.fwnode = fwnode;
+ md->dev.dma_mask = parent->dma_mask;
+
+ dev_set_name(&md->dev, "sdw-master-%d", bus->id);
+
+ ret = device_register(&md->dev);
+ if (ret) {
+ dev_err(parent, "Failed to add master: ret %d\n", ret);
+ /*
+ * On err, don't free but drop ref as this will be freed
+ * when release method is invoked.
+ */
+ put_device(&md->dev);
+ goto device_register_err;
+ }
+
+ /* add shortcuts to improve code readability/compactness */
+ md->bus = bus;
+ bus->dev = &md->dev;
+ bus->md = md;
+
+ pm_runtime_set_autosuspend_delay(&bus->md->dev, SDW_MASTER_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(&bus->md->dev);
+ pm_runtime_mark_last_busy(&bus->md->dev);
+ pm_runtime_set_active(&bus->md->dev);
+ pm_runtime_enable(&bus->md->dev);
+ pm_runtime_idle(&bus->md->dev);
+device_register_err:
+ return ret;
+}
+
+/**
+ * sdw_master_device_del() - delete a Linux Master Device representation.
+ * @bus: bus handle
+ *
+ * This function is the dual of sdw_master_device_add()
+ */
+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
new file mode 100644
index 000000000..55a9c51c8
--- /dev/null
+++ b/drivers/soundwire/mipi_disco.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+/*
+ * MIPI Discovery And Configuration (DisCo) Specification for SoundWire
+ * specifies properties to be implemented for SoundWire Masters and Slaves.
+ * The DisCo spec doesn't mandate these properties. However, SDW bus cannot
+ * work without knowing these values.
+ *
+ * The helper functions read the Master and Slave properties. Implementers
+ * of Master or Slave drivers can use any of the below three mechanisms:
+ * a) Use these APIs here as .read_prop() callback for Master and Slave
+ * b) Implement own methods and set those as .read_prop(), but invoke
+ * APIs in this file for generic read and override the values with
+ * platform specific data
+ * c) Implement ones own methods which do not use anything provided
+ * here
+ */
+
+#include <linux/device.h>
+#include <linux/property.h>
+#include <linux/mod_devicetable.h>
+#include <linux/soundwire/sdw.h>
+#include "bus.h"
+
+/**
+ * sdw_master_read_prop() - Read Master properties
+ * @bus: SDW bus instance
+ */
+int sdw_master_read_prop(struct sdw_bus *bus)
+{
+ struct sdw_master_prop *prop = &bus->prop;
+ struct fwnode_handle *link;
+ char name[32];
+ int nval, i;
+
+ device_property_read_u32(bus->dev,
+ "mipi-sdw-sw-interface-revision",
+ &prop->revision);
+
+ /* Find master handle */
+ snprintf(name, sizeof(name),
+ "mipi-sdw-link-%d-subproperties", bus->link_id);
+
+ link = device_get_named_child_node(bus->dev, name);
+ if (!link) {
+ dev_err(bus->dev, "Master node %s not found\n", name);
+ return -EIO;
+ }
+
+ if (fwnode_property_read_bool(link,
+ "mipi-sdw-clock-stop-mode0-supported"))
+ prop->clk_stop_modes |= BIT(SDW_CLK_STOP_MODE0);
+
+ if (fwnode_property_read_bool(link,
+ "mipi-sdw-clock-stop-mode1-supported"))
+ prop->clk_stop_modes |= BIT(SDW_CLK_STOP_MODE1);
+
+ fwnode_property_read_u32(link,
+ "mipi-sdw-max-clock-frequency",
+ &prop->max_clk_freq);
+
+ nval = fwnode_property_count_u32(link, "mipi-sdw-clock-frequencies-supported");
+ if (nval > 0) {
+ prop->num_clk_freq = nval;
+ prop->clk_freq = devm_kcalloc(bus->dev, prop->num_clk_freq,
+ sizeof(*prop->clk_freq),
+ GFP_KERNEL);
+ if (!prop->clk_freq)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(link,
+ "mipi-sdw-clock-frequencies-supported",
+ prop->clk_freq, prop->num_clk_freq);
+ }
+
+ /*
+ * Check the frequencies supported. If FW doesn't provide max
+ * freq, then populate here by checking values.
+ */
+ if (!prop->max_clk_freq && prop->clk_freq) {
+ prop->max_clk_freq = prop->clk_freq[0];
+ for (i = 1; i < prop->num_clk_freq; i++) {
+ if (prop->clk_freq[i] > prop->max_clk_freq)
+ prop->max_clk_freq = prop->clk_freq[i];
+ }
+ }
+
+ nval = fwnode_property_count_u32(link, "mipi-sdw-supported-clock-gears");
+ if (nval > 0) {
+ prop->num_clk_gears = nval;
+ prop->clk_gears = devm_kcalloc(bus->dev, prop->num_clk_gears,
+ sizeof(*prop->clk_gears),
+ GFP_KERNEL);
+ if (!prop->clk_gears)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(link,
+ "mipi-sdw-supported-clock-gears",
+ prop->clk_gears,
+ prop->num_clk_gears);
+ }
+
+ fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate",
+ &prop->default_frame_rate);
+
+ fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size",
+ &prop->default_row);
+
+ fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size",
+ &prop->default_col);
+
+ prop->dynamic_frame = fwnode_property_read_bool(link,
+ "mipi-sdw-dynamic-frame-shape");
+
+ fwnode_property_read_u32(link, "mipi-sdw-command-error-threshold",
+ &prop->err_threshold);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_master_read_prop);
+
+static int sdw_slave_read_dp0(struct sdw_slave *slave,
+ struct fwnode_handle *port,
+ struct sdw_dp0_prop *dp0)
+{
+ int nval;
+
+ fwnode_property_read_u32(port, "mipi-sdw-port-max-wordlength",
+ &dp0->max_word);
+
+ fwnode_property_read_u32(port, "mipi-sdw-port-min-wordlength",
+ &dp0->min_word);
+
+ nval = fwnode_property_count_u32(port, "mipi-sdw-port-wordlength-configs");
+ if (nval > 0) {
+
+ dp0->num_words = nval;
+ dp0->words = devm_kcalloc(&slave->dev,
+ dp0->num_words, sizeof(*dp0->words),
+ GFP_KERNEL);
+ if (!dp0->words)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(port,
+ "mipi-sdw-port-wordlength-configs",
+ dp0->words, dp0->num_words);
+ }
+
+ dp0->BRA_flow_controlled = fwnode_property_read_bool(port,
+ "mipi-sdw-bra-flow-controlled");
+
+ dp0->simple_ch_prep_sm = fwnode_property_read_bool(port,
+ "mipi-sdw-simplified-channel-prepare-sm");
+
+ dp0->imp_def_interrupts = fwnode_property_read_bool(port,
+ "mipi-sdw-imp-def-dp0-interrupts-supported");
+
+ return 0;
+}
+
+static int sdw_slave_read_dpn(struct sdw_slave *slave,
+ struct sdw_dpn_prop *dpn, int count, int ports,
+ char *type)
+{
+ struct fwnode_handle *node;
+ u32 bit, i = 0;
+ int nval;
+ unsigned long addr;
+ char name[40];
+
+ addr = ports;
+ /* valid ports are 1 to 14 so apply mask */
+ addr &= GENMASK(14, 1);
+
+ for_each_set_bit(bit, &addr, 32) {
+ snprintf(name, sizeof(name),
+ "mipi-sdw-dp-%d-%s-subproperties", bit, type);
+
+ dpn[i].num = bit;
+
+ node = device_get_named_child_node(&slave->dev, name);
+ if (!node) {
+ dev_err(&slave->dev, "%s dpN not found\n", name);
+ return -EIO;
+ }
+
+ fwnode_property_read_u32(node, "mipi-sdw-port-max-wordlength",
+ &dpn[i].max_word);
+ fwnode_property_read_u32(node, "mipi-sdw-port-min-wordlength",
+ &dpn[i].min_word);
+
+ nval = fwnode_property_count_u32(node, "mipi-sdw-port-wordlength-configs");
+ if (nval > 0) {
+ dpn[i].num_words = nval;
+ dpn[i].words = devm_kcalloc(&slave->dev,
+ dpn[i].num_words,
+ sizeof(*dpn[i].words),
+ GFP_KERNEL);
+ if (!dpn[i].words)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(node,
+ "mipi-sdw-port-wordlength-configs",
+ dpn[i].words, dpn[i].num_words);
+ }
+
+ fwnode_property_read_u32(node, "mipi-sdw-data-port-type",
+ &dpn[i].type);
+
+ fwnode_property_read_u32(node,
+ "mipi-sdw-max-grouping-supported",
+ &dpn[i].max_grouping);
+
+ dpn[i].simple_ch_prep_sm = fwnode_property_read_bool(node,
+ "mipi-sdw-simplified-channelprepare-sm");
+
+ fwnode_property_read_u32(node,
+ "mipi-sdw-port-channelprepare-timeout",
+ &dpn[i].ch_prep_timeout);
+
+ fwnode_property_read_u32(node,
+ "mipi-sdw-imp-def-dpn-interrupts-supported",
+ &dpn[i].imp_def_interrupts);
+
+ fwnode_property_read_u32(node, "mipi-sdw-min-channel-number",
+ &dpn[i].min_ch);
+
+ fwnode_property_read_u32(node, "mipi-sdw-max-channel-number",
+ &dpn[i].max_ch);
+
+ nval = fwnode_property_count_u32(node, "mipi-sdw-channel-number-list");
+ if (nval > 0) {
+ dpn[i].num_channels = nval;
+ dpn[i].channels = devm_kcalloc(&slave->dev,
+ dpn[i].num_channels,
+ sizeof(*dpn[i].channels),
+ GFP_KERNEL);
+ if (!dpn[i].channels)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(node,
+ "mipi-sdw-channel-number-list",
+ dpn[i].channels, dpn[i].num_channels);
+ }
+
+ nval = fwnode_property_count_u32(node, "mipi-sdw-channel-combination-list");
+ if (nval > 0) {
+ dpn[i].num_ch_combinations = nval;
+ dpn[i].ch_combinations = devm_kcalloc(&slave->dev,
+ dpn[i].num_ch_combinations,
+ sizeof(*dpn[i].ch_combinations),
+ GFP_KERNEL);
+ if (!dpn[i].ch_combinations)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(node,
+ "mipi-sdw-channel-combination-list",
+ dpn[i].ch_combinations,
+ dpn[i].num_ch_combinations);
+ }
+
+ fwnode_property_read_u32(node,
+ "mipi-sdw-modes-supported", &dpn[i].modes);
+
+ fwnode_property_read_u32(node, "mipi-sdw-max-async-buffer",
+ &dpn[i].max_async_buffer);
+
+ dpn[i].block_pack_mode = fwnode_property_read_bool(node,
+ "mipi-sdw-block-packing-mode");
+
+ fwnode_property_read_u32(node, "mipi-sdw-port-encoding-type",
+ &dpn[i].port_encoding);
+
+ /* TODO: Read audio mode */
+
+ i++;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_slave_read_prop() - Read Slave properties
+ * @slave: SDW Slave
+ */
+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 nval;
+
+ device_property_read_u32(dev, "mipi-sdw-sw-interface-revision",
+ &prop->mipi_revision);
+
+ prop->wake_capable = device_property_read_bool(dev,
+ "mipi-sdw-wake-up-unavailable");
+ prop->wake_capable = !prop->wake_capable;
+
+ prop->test_mode_capable = device_property_read_bool(dev,
+ "mipi-sdw-test-mode-supported");
+
+ prop->clk_stop_mode1 = false;
+ if (device_property_read_bool(dev,
+ "mipi-sdw-clock-stop-mode1-supported"))
+ prop->clk_stop_mode1 = true;
+
+ prop->simple_clk_stop_capable = device_property_read_bool(dev,
+ "mipi-sdw-simplified-clockstopprepare-sm-supported");
+
+ device_property_read_u32(dev, "mipi-sdw-clockstopprepare-timeout",
+ &prop->clk_stop_timeout);
+
+ device_property_read_u32(dev, "mipi-sdw-slave-channelprepare-timeout",
+ &prop->ch_prep_timeout);
+
+ device_property_read_u32(dev,
+ "mipi-sdw-clockstopprepare-hard-reset-behavior",
+ &prop->reset_behave);
+
+ prop->high_PHY_capable = device_property_read_bool(dev,
+ "mipi-sdw-highPHY-capable");
+
+ prop->paging_support = device_property_read_bool(dev,
+ "mipi-sdw-paging-support");
+
+ prop->bank_delay_support = device_property_read_bool(dev,
+ "mipi-sdw-bank-delay-support");
+
+ device_property_read_u32(dev,
+ "mipi-sdw-port15-read-behavior", &prop->p15_behave);
+
+ device_property_read_u32(dev, "mipi-sdw-master-count",
+ &prop->master_count);
+
+ device_property_read_u32(dev, "mipi-sdw-source-port-list",
+ &prop->source_ports);
+
+ device_property_read_u32(dev, "mipi-sdw-sink-port-list",
+ &prop->sink_ports);
+
+ /* Read dp0 properties */
+ port = device_get_named_child_node(dev, "mipi-sdw-dp-0-subproperties");
+ if (!port) {
+ dev_dbg(dev, "DP0 node not found!!\n");
+ } else {
+ prop->dp0_prop = devm_kzalloc(&slave->dev,
+ sizeof(*prop->dp0_prop),
+ GFP_KERNEL);
+ if (!prop->dp0_prop)
+ return -ENOMEM;
+
+ sdw_slave_read_dp0(slave, port, prop->dp0_prop);
+ }
+
+ /*
+ * Based on each DPn port, get source and sink dpn properties.
+ * Also, some ports can operate as both source or sink.
+ */
+
+ /* Allocate memory for set bits in port lists */
+ nval = hweight32(prop->source_ports);
+ prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->src_dpn_prop),
+ GFP_KERNEL);
+ if (!prop->src_dpn_prop)
+ return -ENOMEM;
+
+ /* Read dpn properties for source port(s) */
+ sdw_slave_read_dpn(slave, prop->src_dpn_prop, nval,
+ prop->source_ports, "source");
+
+ nval = hweight32(prop->sink_ports);
+ prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->sink_dpn_prop),
+ GFP_KERNEL);
+ if (!prop->sink_dpn_prop)
+ return -ENOMEM;
+
+ /* Read dpn properties for sink port(s) */
+ sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval,
+ prop->sink_ports, "sink");
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_slave_read_prop);
diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c
new file mode 100644
index 000000000..a51c8a670
--- /dev/null
+++ b/drivers/soundwire/qcom.c
@@ -0,0 +1,1616 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2019, Linaro Limited
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/slimbus.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "bus.h"
+
+#define SWRM_COMP_SW_RESET 0x008
+#define SWRM_COMP_STATUS 0x014
+#define SWRM_FRM_GEN_ENABLED BIT(0)
+#define SWRM_COMP_HW_VERSION 0x00
+#define SWRM_COMP_CFG_ADDR 0x04
+#define SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_MSK BIT(1)
+#define SWRM_COMP_CFG_ENABLE_MSK BIT(0)
+#define SWRM_COMP_PARAMS 0x100
+#define SWRM_COMP_PARAMS_WR_FIFO_DEPTH GENMASK(14, 10)
+#define SWRM_COMP_PARAMS_RD_FIFO_DEPTH GENMASK(19, 15)
+#define SWRM_COMP_PARAMS_DOUT_PORTS_MASK GENMASK(4, 0)
+#define SWRM_COMP_PARAMS_DIN_PORTS_MASK GENMASK(9, 5)
+#define SWRM_COMP_MASTER_ID 0x104
+#define SWRM_INTERRUPT_STATUS 0x200
+#define SWRM_INTERRUPT_STATUS_RMSK GENMASK(16, 0)
+#define SWRM_INTERRUPT_STATUS_SLAVE_PEND_IRQ BIT(0)
+#define SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED BIT(1)
+#define SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS BIT(2)
+#define SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET BIT(3)
+#define SWRM_INTERRUPT_STATUS_RD_FIFO_OVERFLOW BIT(4)
+#define SWRM_INTERRUPT_STATUS_RD_FIFO_UNDERFLOW BIT(5)
+#define SWRM_INTERRUPT_STATUS_WR_CMD_FIFO_OVERFLOW BIT(6)
+#define SWRM_INTERRUPT_STATUS_CMD_ERROR BIT(7)
+#define SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION BIT(8)
+#define SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH BIT(9)
+#define SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED BIT(10)
+#define SWRM_INTERRUPT_STATUS_BUS_RESET_FINISHED_V2 BIT(13)
+#define SWRM_INTERRUPT_STATUS_CLK_STOP_FINISHED_V2 BIT(14)
+#define SWRM_INTERRUPT_STATUS_EXT_CLK_STOP_WAKEUP BIT(16)
+#define SWRM_INTERRUPT_MAX 17
+#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
+#define SWRM_CMD_FIFO_FLUSH 0x1
+#define SWRM_CMD_FIFO_STATUS 0x30C
+#define SWRM_RD_CMD_FIFO_CNT_MASK GENMASK(20, 16)
+#define SWRM_WR_CMD_FIFO_CNT_MASK GENMASK(12, 8)
+#define SWRM_CMD_FIFO_CFG_ADDR 0x314
+#define SWRM_CONTINUE_EXEC_ON_CMD_IGNORE BIT(31)
+#define SWRM_RD_WR_CMD_RETRIES 0x7
+#define SWRM_CMD_FIFO_RD_FIFO_ADDR 0x318
+#define SWRM_RD_FIFO_CMD_ID_MASK GENMASK(11, 8)
+#define SWRM_ENUMERATOR_CFG_ADDR 0x500
+#define SWRM_ENUMERATOR_SLAVE_DEV_ID_1(m) (0x530 + 0x8 * (m))
+#define SWRM_ENUMERATOR_SLAVE_DEV_ID_2(m) (0x534 + 0x8 * (m))
+#define SWRM_MCP_FRAME_CTRL_BANK_ADDR(m) (0x101C + 0x40 * (m))
+#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_BUS_CTRL 0x1044
+#define SWRM_MCP_BUS_CLK_START BIT(1)
+#define SWRM_MCP_CFG_ADDR 0x1048
+#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK GENMASK(21, 17)
+#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_MCP_SLV_STATUS_SZ 2
+#define SWRM_DP_PORT_CTRL_BANK(n, m) (0x1124 + 0x100 * (n - 1) + 0x40 * m)
+#define SWRM_DP_PORT_CTRL_2_BANK(n, m) (0x1128 + 0x100 * (n - 1) + 0x40 * m)
+#define SWRM_DP_BLOCK_CTRL_1(n) (0x112C + 0x100 * (n - 1))
+#define SWRM_DP_BLOCK_CTRL2_BANK(n, m) (0x1130 + 0x100 * (n - 1) + 0x40 * m)
+#define SWRM_DP_PORT_HCTRL_BANK(n, m) (0x1134 + 0x100 * (n - 1) + 0x40 * m)
+#define SWRM_DP_BLOCK_CTRL3_BANK(n, m) (0x1138 + 0x100 * (n - 1) + 0x40 * m)
+#define SWRM_DIN_DPn_PCM_PORT_CTRL(n) (0x1054 + 0x100 * (n - 1))
+#define SWR_MSTR_MAX_REG_ADDR (0x1740)
+
+#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
+#define SWRM_AHB_BRIDGE_WR_DATA_0 0xc85
+#define SWRM_AHB_BRIDGE_WR_ADDR_0 0xc89
+#define SWRM_AHB_BRIDGE_RD_ADDR_0 0xc8d
+#define SWRM_AHB_BRIDGE_RD_DATA_0 0xc91
+
+#define SWRM_REG_VAL_PACK(data, dev, id, reg) \
+ ((reg) | ((id) << 16) | ((dev) << 20) | ((data) << 24))
+
+#define SWRM_SPECIAL_CMD_ID 0xF
+#define MAX_FREQ_NUM 1
+#define TIMEOUT_MS 100
+#define QCOM_SWRM_MAX_RD_LEN 0x1
+#define QCOM_SDW_MAX_PORTS 14
+#define DEFAULT_CLK_FREQ 9600000
+#define SWRM_MAX_DAIS 0xF
+#define SWR_INVALID_PARAM 0xFF
+#define SWR_HSTOP_MAX_VAL 0xF
+#define SWR_HSTART_MIN_VAL 0x0
+#define SWR_BROADCAST_CMD_ID 0x0F
+#define SWR_MAX_CMD_ID 14
+#define MAX_FIFO_RD_RETRY 3
+#define SWR_OVERFLOW_RETRY_COUNT 30
+#define SWRM_LINK_STATUS_RETRY_CNT 100
+
+enum {
+ MASTER_ID_WSA = 1,
+ MASTER_ID_RX,
+ MASTER_ID_TX
+};
+
+struct qcom_swrm_port_config {
+ u8 si;
+ u8 off1;
+ u8 off2;
+ u8 bp_mode;
+ u8 hstart;
+ u8 hstop;
+ u8 word_length;
+ u8 blk_group_count;
+ u8 lane_control;
+};
+
+struct qcom_swrm_ctrl {
+ struct sdw_bus bus;
+ struct device *dev;
+ struct regmap *regmap;
+ void __iomem *mmio;
+ struct reset_control *audio_cgcr;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs;
+#endif
+ struct completion broadcast;
+ struct completion enumeration;
+ struct work_struct slave_work;
+ /* Port alloc/free lock */
+ struct mutex port_lock;
+ struct clk *hclk;
+ u8 wr_cmd_id;
+ u8 rd_cmd_id;
+ int irq;
+ unsigned int version;
+ int wake_irq;
+ int num_din_ports;
+ int num_dout_ports;
+ int cols_index;
+ int rows_index;
+ unsigned long dout_port_mask;
+ unsigned long din_port_mask;
+ u32 intr_mask;
+ u8 rcmd_id;
+ u8 wcmd_id;
+ /* Port numbers are 1 - 14 */
+ struct qcom_swrm_port_config pconfig[QCOM_SDW_MAX_PORTS + 1];
+ struct sdw_stream_runtime *sruntime[SWRM_MAX_DAIS];
+ enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
+ int (*reg_read)(struct qcom_swrm_ctrl *ctrl, int reg, u32 *val);
+ int (*reg_write)(struct qcom_swrm_ctrl *ctrl, int reg, int val);
+ u32 slave_status;
+ u32 wr_fifo_depth;
+ u32 rd_fifo_depth;
+ bool clock_stop_not_supported;
+};
+
+struct qcom_swrm_data {
+ u32 default_cols;
+ u32 default_rows;
+ bool sw_clk_gate_required;
+};
+
+static const struct qcom_swrm_data swrm_v1_3_data = {
+ .default_rows = 48,
+ .default_cols = 16,
+};
+
+static const struct qcom_swrm_data swrm_v1_5_data = {
+ .default_rows = 50,
+ .default_cols = 16,
+};
+
+static const struct qcom_swrm_data swrm_v1_6_data = {
+ .default_rows = 50,
+ .default_cols = 16,
+ .sw_clk_gate_required = true,
+};
+
+#define to_qcom_sdw(b) container_of(b, struct qcom_swrm_ctrl, bus)
+
+static int qcom_swrm_ahb_reg_read(struct qcom_swrm_ctrl *ctrl, int reg,
+ u32 *val)
+{
+ struct regmap *wcd_regmap = ctrl->regmap;
+ int ret;
+
+ /* pg register + offset */
+ ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_RD_ADDR_0,
+ (u8 *)&reg, 4);
+ if (ret < 0)
+ return SDW_CMD_FAIL;
+
+ ret = regmap_bulk_read(wcd_regmap, SWRM_AHB_BRIDGE_RD_DATA_0,
+ val, 4);
+ if (ret < 0)
+ return SDW_CMD_FAIL;
+
+ return SDW_CMD_OK;
+}
+
+static int qcom_swrm_ahb_reg_write(struct qcom_swrm_ctrl *ctrl,
+ int reg, int val)
+{
+ struct regmap *wcd_regmap = ctrl->regmap;
+ int ret;
+ /* pg register + offset */
+ ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_WR_DATA_0,
+ (u8 *)&val, 4);
+ if (ret)
+ return SDW_CMD_FAIL;
+
+ /* write address register */
+ ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_WR_ADDR_0,
+ (u8 *)&reg, 4);
+ if (ret)
+ return SDW_CMD_FAIL;
+
+ 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 u32 swrm_get_packed_reg_val(u8 *cmd_id, u8 cmd_data,
+ u8 dev_addr, u16 reg_addr)
+{
+ u32 val;
+ u8 id = *cmd_id;
+
+ if (id != SWR_BROADCAST_CMD_ID) {
+ if (id < SWR_MAX_CMD_ID)
+ id += 1;
+ else
+ id = 0;
+ *cmd_id = id;
+ }
+ val = SWRM_REG_VAL_PACK(cmd_data, dev_addr, id, reg_addr);
+
+ return val;
+}
+
+static int swrm_wait_for_rd_fifo_avail(struct qcom_swrm_ctrl *swrm)
+{
+ u32 fifo_outstanding_data, value;
+ int fifo_retry_count = SWR_OVERFLOW_RETRY_COUNT;
+
+ do {
+ /* Check for fifo underflow during read */
+ swrm->reg_read(swrm, SWRM_CMD_FIFO_STATUS, &value);
+ fifo_outstanding_data = FIELD_GET(SWRM_RD_CMD_FIFO_CNT_MASK, value);
+
+ /* Check if read data is available in read fifo */
+ if (fifo_outstanding_data > 0)
+ return 0;
+
+ usleep_range(500, 510);
+ } while (fifo_retry_count--);
+
+ if (fifo_outstanding_data == 0) {
+ dev_err_ratelimited(swrm->dev, "%s err read underflow\n", __func__);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int swrm_wait_for_wr_fifo_avail(struct qcom_swrm_ctrl *swrm)
+{
+ u32 fifo_outstanding_cmds, value;
+ int fifo_retry_count = SWR_OVERFLOW_RETRY_COUNT;
+
+ do {
+ /* Check for fifo overflow during write */
+ swrm->reg_read(swrm, SWRM_CMD_FIFO_STATUS, &value);
+ fifo_outstanding_cmds = FIELD_GET(SWRM_WR_CMD_FIFO_CNT_MASK, value);
+
+ /* Check for space in write fifo before writing */
+ if (fifo_outstanding_cmds < swrm->wr_fifo_depth)
+ return 0;
+
+ usleep_range(500, 510);
+ } while (fifo_retry_count--);
+
+ if (fifo_outstanding_cmds == swrm->wr_fifo_depth) {
+ dev_err_ratelimited(swrm->dev, "%s err write overflow\n", __func__);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int qcom_swrm_cmd_fifo_wr_cmd(struct qcom_swrm_ctrl *swrm, u8 cmd_data,
+ u8 dev_addr, u16 reg_addr)
+{
+
+ u32 val;
+ int ret = 0;
+ u8 cmd_id = 0x0;
+
+ if (dev_addr == SDW_BROADCAST_DEV_NUM) {
+ cmd_id = SWR_BROADCAST_CMD_ID;
+ val = swrm_get_packed_reg_val(&cmd_id, cmd_data,
+ dev_addr, reg_addr);
+ } else {
+ val = swrm_get_packed_reg_val(&swrm->wcmd_id, cmd_data,
+ dev_addr, reg_addr);
+ }
+
+ if (swrm_wait_for_wr_fifo_avail(swrm))
+ return SDW_CMD_FAIL_OTHER;
+
+ if (cmd_id == SWR_BROADCAST_CMD_ID)
+ reinit_completion(&swrm->broadcast);
+
+ /* Its assumed that write is okay as we do not get any status back */
+ swrm->reg_write(swrm, SWRM_CMD_FIFO_WR_CMD, val);
+
+ /* version 1.3 or less */
+ if (swrm->version <= 0x01030000)
+ usleep_range(150, 155);
+
+ if (cmd_id == SWR_BROADCAST_CMD_ID) {
+ /*
+ * sleep for 10ms for MSM soundwire variant to allow broadcast
+ * command to complete.
+ */
+ ret = wait_for_completion_timeout(&swrm->broadcast,
+ msecs_to_jiffies(TIMEOUT_MS));
+ if (!ret)
+ ret = SDW_CMD_IGNORED;
+ else
+ ret = SDW_CMD_OK;
+
+ } else {
+ ret = SDW_CMD_OK;
+ }
+ return ret;
+}
+
+static int qcom_swrm_cmd_fifo_rd_cmd(struct qcom_swrm_ctrl *swrm,
+ u8 dev_addr, u16 reg_addr,
+ u32 len, u8 *rval)
+{
+ u32 cmd_data, cmd_id, val, retry_attempt = 0;
+
+ val = swrm_get_packed_reg_val(&swrm->rcmd_id, len, dev_addr, reg_addr);
+
+ /*
+ * Check for outstanding cmd wrt. write fifo depth to avoid
+ * overflow as read will also increase write fifo cnt.
+ */
+ swrm_wait_for_wr_fifo_avail(swrm);
+
+ /* wait for FIFO RD to complete to avoid overflow */
+ usleep_range(100, 105);
+ swrm->reg_write(swrm, SWRM_CMD_FIFO_RD_CMD, val);
+ /* wait for FIFO RD CMD complete to avoid overflow */
+ usleep_range(250, 255);
+
+ if (swrm_wait_for_rd_fifo_avail(swrm))
+ return SDW_CMD_FAIL_OTHER;
+
+ do {
+ swrm->reg_read(swrm, SWRM_CMD_FIFO_RD_FIFO_ADDR, &cmd_data);
+ rval[0] = cmd_data & 0xFF;
+ cmd_id = FIELD_GET(SWRM_RD_FIFO_CMD_ID_MASK, cmd_data);
+
+ if (cmd_id != swrm->rcmd_id) {
+ if (retry_attempt < (MAX_FIFO_RD_RETRY - 1)) {
+ /* wait 500 us before retry on fifo read failure */
+ usleep_range(500, 505);
+ swrm->reg_write(swrm, SWRM_CMD_FIFO_CMD,
+ SWRM_CMD_FIFO_FLUSH);
+ swrm->reg_write(swrm, SWRM_CMD_FIFO_RD_CMD, val);
+ }
+ retry_attempt++;
+ } else {
+ return SDW_CMD_OK;
+ }
+
+ } while (retry_attempt < MAX_FIFO_RD_RETRY);
+
+ dev_err(swrm->dev, "failed to read fifo: reg: 0x%x, rcmd_id: 0x%x,\
+ dev_num: 0x%x, cmd_data: 0x%x\n",
+ reg_addr, swrm->rcmd_id, dev_addr, cmd_data);
+
+ return SDW_CMD_IGNORED;
+}
+
+static int qcom_swrm_get_alert_slave_dev_num(struct qcom_swrm_ctrl *ctrl)
+{
+ u32 val, status;
+ int dev_num;
+
+ ctrl->reg_read(ctrl, SWRM_MCP_SLV_STATUS, &val);
+
+ for (dev_num = 1; dev_num <= SDW_MAX_DEVICES; dev_num++) {
+ status = (val >> (dev_num * SWRM_MCP_SLV_STATUS_SZ));
+
+ if ((status & SWRM_MCP_SLV_STATUS_MASK) == SDW_SLAVE_ALERT) {
+ ctrl->status[dev_num] = status & SWRM_MCP_SLV_STATUS_MASK;
+ return dev_num;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static void qcom_swrm_get_device_status(struct qcom_swrm_ctrl *ctrl)
+{
+ u32 val;
+ int i;
+
+ ctrl->reg_read(ctrl, SWRM_MCP_SLV_STATUS, &val);
+ ctrl->slave_status = val;
+
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+ u32 s;
+
+ s = (val >> (i * 2));
+ s &= SWRM_MCP_SLV_STATUS_MASK;
+ ctrl->status[i] = s;
+ }
+}
+
+static void qcom_swrm_set_slave_dev_num(struct sdw_bus *bus,
+ struct sdw_slave *slave, int devnum)
+{
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+ u32 status;
+
+ ctrl->reg_read(ctrl, SWRM_MCP_SLV_STATUS, &status);
+ status = (status >> (devnum * SWRM_MCP_SLV_STATUS_SZ));
+ status &= SWRM_MCP_SLV_STATUS_MASK;
+
+ if (status == SDW_SLAVE_ATTACHED) {
+ if (slave)
+ slave->dev_num = devnum;
+ mutex_lock(&bus->bus_lock);
+ set_bit(devnum, bus->assigned);
+ mutex_unlock(&bus->bus_lock);
+ }
+}
+
+static int qcom_swrm_enumerate(struct sdw_bus *bus)
+{
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+ struct sdw_slave *slave, *_s;
+ struct sdw_slave_id id;
+ u32 val1, val2;
+ bool found;
+ u64 addr;
+ int i;
+ char *buf1 = (char *)&val1, *buf2 = (char *)&val2;
+
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+ /* do not continue if the status is Not Present */
+ if (!ctrl->status[i])
+ continue;
+
+ /*SCP_Devid5 - Devid 4*/
+ ctrl->reg_read(ctrl, SWRM_ENUMERATOR_SLAVE_DEV_ID_1(i), &val1);
+
+ /*SCP_Devid3 - DevId 2 Devid 1 Devid 0*/
+ ctrl->reg_read(ctrl, SWRM_ENUMERATOR_SLAVE_DEV_ID_2(i), &val2);
+
+ if (!val1 && !val2)
+ break;
+
+ addr = buf2[1] | (buf2[0] << 8) | (buf1[3] << 16) |
+ ((u64)buf1[2] << 24) | ((u64)buf1[1] << 32) |
+ ((u64)buf1[0] << 40);
+
+ sdw_extract_slave_id(bus, addr, &id);
+ found = false;
+ /* Now compare with entries */
+ list_for_each_entry_safe(slave, _s, &bus->slaves, node) {
+ if (sdw_compare_devid(slave, id) == 0) {
+ qcom_swrm_set_slave_dev_num(bus, slave, i);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ qcom_swrm_set_slave_dev_num(bus, NULL, i);
+ sdw_slave_add(bus, &id, NULL);
+ }
+ }
+
+ complete(&ctrl->enumeration);
+ return 0;
+}
+
+static irqreturn_t qcom_swrm_wake_irq_handler(int irq, void *dev_id)
+{
+ struct qcom_swrm_ctrl *swrm = dev_id;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(swrm->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(swrm->dev,
+ "pm_runtime_resume_and_get failed in %s, ret %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ if (swrm->wake_irq > 0) {
+ if (!irqd_irq_disabled(irq_get_irq_data(swrm->wake_irq)))
+ disable_irq_nosync(swrm->wake_irq);
+ }
+
+ pm_runtime_mark_last_busy(swrm->dev);
+ pm_runtime_put_autosuspend(swrm->dev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)
+{
+ struct qcom_swrm_ctrl *swrm = dev_id;
+ u32 value, intr_sts, intr_sts_masked, slave_status;
+ u32 i;
+ int devnum;
+ int ret = IRQ_HANDLED;
+ clk_prepare_enable(swrm->hclk);
+
+ swrm->reg_read(swrm, SWRM_INTERRUPT_STATUS, &intr_sts);
+ intr_sts_masked = intr_sts & swrm->intr_mask;
+
+ do {
+ for (i = 0; i < SWRM_INTERRUPT_MAX; i++) {
+ value = intr_sts_masked & BIT(i);
+ if (!value)
+ continue;
+
+ switch (value) {
+ case SWRM_INTERRUPT_STATUS_SLAVE_PEND_IRQ:
+ devnum = qcom_swrm_get_alert_slave_dev_num(swrm);
+ if (devnum < 0) {
+ dev_err_ratelimited(swrm->dev,
+ "no slave alert found.spurious interrupt\n");
+ } else {
+ sdw_handle_slave_status(&swrm->bus, swrm->status);
+ }
+
+ break;
+ case SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED:
+ case SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS:
+ dev_dbg_ratelimited(swrm->dev, "SWR new slave attached\n");
+ swrm->reg_read(swrm, SWRM_MCP_SLV_STATUS, &slave_status);
+ if (swrm->slave_status == slave_status) {
+ dev_dbg(swrm->dev, "Slave status not changed %x\n",
+ slave_status);
+ } else {
+ qcom_swrm_get_device_status(swrm);
+ qcom_swrm_enumerate(&swrm->bus);
+ sdw_handle_slave_status(&swrm->bus, swrm->status);
+ }
+ break;
+ case SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET:
+ dev_err_ratelimited(swrm->dev,
+ "%s: SWR bus clsh detected\n",
+ __func__);
+ swrm->intr_mask &= ~SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
+ swrm->reg_write(swrm, SWRM_INTERRUPT_CPU_EN, swrm->intr_mask);
+ break;
+ case SWRM_INTERRUPT_STATUS_RD_FIFO_OVERFLOW:
+ swrm->reg_read(swrm, SWRM_CMD_FIFO_STATUS, &value);
+ dev_err_ratelimited(swrm->dev,
+ "%s: SWR read FIFO overflow fifo status 0x%x\n",
+ __func__, value);
+ break;
+ case SWRM_INTERRUPT_STATUS_RD_FIFO_UNDERFLOW:
+ swrm->reg_read(swrm, SWRM_CMD_FIFO_STATUS, &value);
+ dev_err_ratelimited(swrm->dev,
+ "%s: SWR read FIFO underflow fifo status 0x%x\n",
+ __func__, value);
+ break;
+ case SWRM_INTERRUPT_STATUS_WR_CMD_FIFO_OVERFLOW:
+ swrm->reg_read(swrm, SWRM_CMD_FIFO_STATUS, &value);
+ dev_err(swrm->dev,
+ "%s: SWR write FIFO overflow fifo status %x\n",
+ __func__, value);
+ swrm->reg_write(swrm, SWRM_CMD_FIFO_CMD, 0x1);
+ break;
+ case SWRM_INTERRUPT_STATUS_CMD_ERROR:
+ swrm->reg_read(swrm, SWRM_CMD_FIFO_STATUS, &value);
+ dev_err_ratelimited(swrm->dev,
+ "%s: SWR CMD error, fifo status 0x%x, flushing fifo\n",
+ __func__, value);
+ swrm->reg_write(swrm, SWRM_CMD_FIFO_CMD, 0x1);
+ break;
+ case SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION:
+ dev_err_ratelimited(swrm->dev,
+ "%s: SWR Port collision detected\n",
+ __func__);
+ swrm->intr_mask &= ~SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION;
+ swrm->reg_write(swrm,
+ SWRM_INTERRUPT_CPU_EN, swrm->intr_mask);
+ break;
+ case SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH:
+ dev_err_ratelimited(swrm->dev,
+ "%s: SWR read enable valid mismatch\n",
+ __func__);
+ swrm->intr_mask &=
+ ~SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH;
+ swrm->reg_write(swrm,
+ SWRM_INTERRUPT_CPU_EN, swrm->intr_mask);
+ break;
+ case SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED:
+ complete(&swrm->broadcast);
+ break;
+ case SWRM_INTERRUPT_STATUS_BUS_RESET_FINISHED_V2:
+ break;
+ case SWRM_INTERRUPT_STATUS_CLK_STOP_FINISHED_V2:
+ break;
+ case SWRM_INTERRUPT_STATUS_EXT_CLK_STOP_WAKEUP:
+ break;
+ default:
+ dev_err_ratelimited(swrm->dev,
+ "%s: SWR unknown interrupt value: %d\n",
+ __func__, value);
+ ret = IRQ_NONE;
+ break;
+ }
+ }
+ swrm->reg_write(swrm, SWRM_INTERRUPT_CLEAR, intr_sts);
+ swrm->reg_read(swrm, SWRM_INTERRUPT_STATUS, &intr_sts);
+ intr_sts_masked = intr_sts & swrm->intr_mask;
+ } while (intr_sts_masked);
+
+ clk_disable_unprepare(swrm->hclk);
+ return ret;
+}
+
+static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl)
+{
+ u32 val;
+
+ /* Clear Rows and Cols */
+ 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);
+
+ reset_control_reset(ctrl->audio_cgcr);
+
+ ctrl->reg_write(ctrl, SWRM_MCP_FRAME_CTRL_BANK_ADDR(0), val);
+
+ /* Enable Auto enumeration */
+ ctrl->reg_write(ctrl, SWRM_ENUMERATOR_CFG_ADDR, 1);
+
+ ctrl->intr_mask = SWRM_INTERRUPT_STATUS_RMSK;
+ /* Mask soundwire interrupts */
+ ctrl->reg_write(ctrl, SWRM_INTERRUPT_MASK_ADDR,
+ SWRM_INTERRUPT_STATUS_RMSK);
+
+ /* Configure No pings */
+ ctrl->reg_read(ctrl, SWRM_MCP_CFG_ADDR, &val);
+ 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);
+
+ ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START);
+ /* Configure number of retries of a read/write cmd */
+ if (ctrl->version >= 0x01050001) {
+ /* Only for versions >= 1.5.1 */
+ ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CFG_ADDR,
+ SWRM_RD_WR_CMD_RETRIES |
+ SWRM_CONTINUE_EXEC_ON_CMD_IGNORE);
+ } else {
+ ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CFG_ADDR,
+ SWRM_RD_WR_CMD_RETRIES);
+ }
+
+ /* Set IRQ to PULSE */
+ 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);
+ }
+ ctrl->slave_status = 0;
+ ctrl->reg_read(ctrl, SWRM_COMP_PARAMS, &val);
+ ctrl->rd_fifo_depth = FIELD_GET(SWRM_COMP_PARAMS_RD_FIFO_DEPTH, val);
+ ctrl->wr_fifo_depth = FIELD_GET(SWRM_COMP_PARAMS_WR_FIFO_DEPTH, val);
+
+ return 0;
+}
+
+static enum sdw_command_response qcom_swrm_xfer_msg(struct sdw_bus *bus,
+ struct sdw_msg *msg)
+{
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+ int ret, i, len;
+
+ if (msg->flags == SDW_MSG_FLAG_READ) {
+ for (i = 0; i < msg->len;) {
+ if ((msg->len - i) < QCOM_SWRM_MAX_RD_LEN)
+ len = msg->len - i;
+ else
+ len = QCOM_SWRM_MAX_RD_LEN;
+
+ ret = qcom_swrm_cmd_fifo_rd_cmd(ctrl, msg->dev_num,
+ msg->addr + i, len,
+ &msg->buf[i]);
+ if (ret)
+ return ret;
+
+ i = i + len;
+ }
+ } else if (msg->flags == SDW_MSG_FLAG_WRITE) {
+ for (i = 0; i < msg->len; i++) {
+ ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->buf[i],
+ msg->dev_num,
+ msg->addr + i);
+ if (ret)
+ return SDW_CMD_IGNORED;
+ }
+ }
+
+ return SDW_CMD_OK;
+}
+
+static int qcom_swrm_pre_bank_switch(struct sdw_bus *bus)
+{
+ u32 reg = SWRM_MCP_FRAME_CTRL_BANK_ADDR(bus->params.next_bank);
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+ u32 val;
+
+ ctrl->reg_read(ctrl, reg, &val);
+
+ 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);
+}
+
+static int qcom_swrm_port_params(struct sdw_bus *bus,
+ struct sdw_port_params *p_params,
+ unsigned int bank)
+{
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+
+ return ctrl->reg_write(ctrl, SWRM_DP_BLOCK_CTRL_1(p_params->num),
+ p_params->bps - 1);
+
+}
+
+static int qcom_swrm_transport_params(struct sdw_bus *bus,
+ struct sdw_transport_params *params,
+ enum sdw_reg_bank bank)
+{
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+ struct qcom_swrm_port_config *pcfg;
+ u32 value;
+ int reg = SWRM_DP_PORT_CTRL_BANK((params->port_num), bank);
+ int ret;
+
+ pcfg = &ctrl->pconfig[params->port_num];
+
+ value = pcfg->off1 << SWRM_DP_PORT_CTRL_OFFSET1_SHFT;
+ value |= pcfg->off2 << SWRM_DP_PORT_CTRL_OFFSET2_SHFT;
+ value |= pcfg->si;
+
+ ret = ctrl->reg_write(ctrl, reg, value);
+ if (ret)
+ goto err;
+
+ if (pcfg->lane_control != SWR_INVALID_PARAM) {
+ reg = SWRM_DP_PORT_CTRL_2_BANK(params->port_num, bank);
+ value = pcfg->lane_control;
+ ret = ctrl->reg_write(ctrl, reg, value);
+ if (ret)
+ goto err;
+ }
+
+ if (pcfg->blk_group_count != SWR_INVALID_PARAM) {
+ reg = SWRM_DP_BLOCK_CTRL2_BANK(params->port_num, bank);
+ value = pcfg->blk_group_count;
+ ret = ctrl->reg_write(ctrl, reg, value);
+ if (ret)
+ goto err;
+ }
+
+ if (pcfg->hstart != SWR_INVALID_PARAM
+ && pcfg->hstop != SWR_INVALID_PARAM) {
+ reg = SWRM_DP_PORT_HCTRL_BANK(params->port_num, bank);
+ value = (pcfg->hstop << 4) | pcfg->hstart;
+ ret = ctrl->reg_write(ctrl, reg, value);
+ } else {
+ reg = SWRM_DP_PORT_HCTRL_BANK(params->port_num, bank);
+ value = (SWR_HSTOP_MAX_VAL << 4) | SWR_HSTART_MIN_VAL;
+ ret = ctrl->reg_write(ctrl, reg, value);
+ }
+
+ if (ret)
+ goto err;
+
+ if (pcfg->bp_mode != SWR_INVALID_PARAM) {
+ reg = SWRM_DP_BLOCK_CTRL3_BANK(params->port_num, bank);
+ ret = ctrl->reg_write(ctrl, reg, pcfg->bp_mode);
+ }
+
+err:
+ return ret;
+}
+
+static int qcom_swrm_port_enable(struct sdw_bus *bus,
+ struct sdw_enable_ch *enable_ch,
+ unsigned int bank)
+{
+ u32 reg = SWRM_DP_PORT_CTRL_BANK(enable_ch->port_num, bank);
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+ u32 val;
+
+ ctrl->reg_read(ctrl, reg, &val);
+
+ if (enable_ch->enable)
+ val |= (enable_ch->ch_mask << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT);
+ else
+ val &= ~(0xff << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT);
+
+ return ctrl->reg_write(ctrl, reg, val);
+}
+
+static const struct sdw_master_port_ops qcom_swrm_port_ops = {
+ .dpn_set_port_params = qcom_swrm_port_params,
+ .dpn_set_port_transport_params = qcom_swrm_transport_params,
+ .dpn_port_enable_ch = qcom_swrm_port_enable,
+};
+
+static const struct sdw_master_ops qcom_swrm_ops = {
+ .xfer_msg = qcom_swrm_xfer_msg,
+ .pre_bank_switch = qcom_swrm_pre_bank_switch,
+};
+
+static int qcom_swrm_compute_params(struct sdw_bus *bus)
+{
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+ struct sdw_master_runtime *m_rt;
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_port_runtime *p_rt;
+ struct qcom_swrm_port_config *pcfg;
+ struct sdw_slave *slave;
+ unsigned int m_port;
+ int i = 1;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ pcfg = &ctrl->pconfig[p_rt->num];
+ p_rt->transport_params.port_num = p_rt->num;
+ if (pcfg->word_length != SWR_INVALID_PARAM) {
+ sdw_fill_port_params(&p_rt->port_params,
+ p_rt->num, pcfg->word_length + 1,
+ SDW_PORT_FLOW_MODE_ISOCH,
+ SDW_PORT_DATA_MODE_NORMAL);
+ }
+
+ }
+
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ slave = s_rt->slave;
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ m_port = slave->m_port_map[p_rt->num];
+ /* port config starts at offset 0 so -1 from actual port number */
+ if (m_port)
+ pcfg = &ctrl->pconfig[m_port];
+ else
+ pcfg = &ctrl->pconfig[i];
+ p_rt->transport_params.port_num = p_rt->num;
+ 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;
+ p_rt->transport_params.blk_grp_ctrl = pcfg->blk_group_count;
+
+ p_rt->transport_params.hstart = pcfg->hstart;
+ p_rt->transport_params.hstop = pcfg->hstop;
+ p_rt->transport_params.lane_ctrl = pcfg->lane_control;
+ if (pcfg->word_length != SWR_INVALID_PARAM) {
+ sdw_fill_port_params(&p_rt->port_params,
+ p_rt->num,
+ pcfg->word_length + 1,
+ SDW_PORT_FLOW_MODE_ISOCH,
+ SDW_PORT_DATA_MODE_NORMAL);
+ }
+ i++;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static u32 qcom_swrm_freq_tbl[MAX_FREQ_NUM] = {
+ DEFAULT_CLK_FREQ,
+};
+
+static void qcom_swrm_stream_free_ports(struct qcom_swrm_ctrl *ctrl,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_port_runtime *p_rt;
+ unsigned long *port_mask;
+
+ mutex_lock(&ctrl->port_lock);
+
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ if (m_rt->direction == SDW_DATA_DIR_RX)
+ port_mask = &ctrl->dout_port_mask;
+ else
+ port_mask = &ctrl->din_port_mask;
+
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node)
+ clear_bit(p_rt->num, port_mask);
+ }
+
+ mutex_unlock(&ctrl->port_lock);
+}
+
+static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl,
+ struct sdw_stream_runtime *stream,
+ struct snd_pcm_hw_params *params,
+ int direction)
+{
+ struct sdw_port_config pconfig[QCOM_SDW_MAX_PORTS];
+ struct sdw_stream_config sconfig;
+ struct sdw_master_runtime *m_rt;
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_port_runtime *p_rt;
+ struct sdw_slave *slave;
+ unsigned long *port_mask;
+ int i, maxport, pn, nports = 0, ret = 0;
+ unsigned int m_port;
+
+ mutex_lock(&ctrl->port_lock);
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ if (m_rt->direction == SDW_DATA_DIR_RX) {
+ maxport = ctrl->num_dout_ports;
+ port_mask = &ctrl->dout_port_mask;
+ } else {
+ maxport = ctrl->num_din_ports;
+ port_mask = &ctrl->din_port_mask;
+ }
+
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ slave = s_rt->slave;
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ m_port = slave->m_port_map[p_rt->num];
+ /* Port numbers start from 1 - 14*/
+ if (m_port)
+ pn = m_port;
+ else
+ pn = find_first_zero_bit(port_mask, maxport);
+
+ if (pn > maxport) {
+ dev_err(ctrl->dev, "All ports busy\n");
+ ret = -EBUSY;
+ goto err;
+ }
+ set_bit(pn, port_mask);
+ pconfig[nports].num = pn;
+ pconfig[nports].ch_mask = p_rt->ch_mask;
+ nports++;
+ }
+ }
+ }
+
+ if (direction == SNDRV_PCM_STREAM_CAPTURE)
+ sconfig.direction = SDW_DATA_DIR_TX;
+ else
+ sconfig.direction = SDW_DATA_DIR_RX;
+
+ /* hw parameters wil be ignored as we only support PDM */
+ sconfig.ch_count = 1;
+ sconfig.frame_rate = params_rate(params);
+ sconfig.type = stream->type;
+ sconfig.bps = 1;
+ sdw_stream_add_master(&ctrl->bus, &sconfig, pconfig,
+ nports, stream);
+err:
+ if (ret) {
+ for (i = 0; i < nports; i++)
+ clear_bit(pconfig[i].num, port_mask);
+ }
+
+ mutex_unlock(&ctrl->port_lock);
+
+ return ret;
+}
+
+static int qcom_swrm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev);
+ struct sdw_stream_runtime *sruntime = ctrl->sruntime[dai->id];
+ int ret;
+
+ ret = qcom_swrm_stream_alloc_ports(ctrl, sruntime, params,
+ substream->stream);
+ if (ret)
+ qcom_swrm_stream_free_ports(ctrl, sruntime);
+
+ return ret;
+}
+
+static int qcom_swrm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev);
+ struct sdw_stream_runtime *sruntime = ctrl->sruntime[dai->id];
+
+ qcom_swrm_stream_free_ports(ctrl, sruntime);
+ sdw_stream_remove_master(&ctrl->bus, sruntime);
+
+ return 0;
+}
+
+static int qcom_swrm_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, int direction)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev);
+
+ ctrl->sruntime[dai->id] = stream;
+
+ return 0;
+}
+
+static void *qcom_swrm_get_sdw_stream(struct snd_soc_dai *dai, int direction)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev);
+
+ return ctrl->sruntime[dai->id];
+}
+
+static int qcom_swrm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev);
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sdw_stream_runtime *sruntime;
+ struct snd_soc_dai *codec_dai;
+ int ret, i;
+
+ ret = pm_runtime_resume_and_get(ctrl->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(ctrl->dev,
+ "pm_runtime_resume_and_get failed in %s, ret %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ sruntime = sdw_alloc_stream(dai->name);
+ if (!sruntime) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ ctrl->sruntime[dai->id] = sruntime;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ ret = snd_soc_dai_set_stream(codec_dai, sruntime,
+ substream->stream);
+ if (ret < 0 && ret != -ENOTSUPP) {
+ dev_err(dai->dev, "Failed to set sdw stream on %s\n",
+ codec_dai->name);
+ goto err_set_stream;
+ }
+ }
+
+ return 0;
+
+err_set_stream:
+ sdw_release_stream(sruntime);
+err_alloc:
+ pm_runtime_mark_last_busy(ctrl->dev);
+ pm_runtime_put_autosuspend(ctrl->dev);
+
+ return ret;
+}
+
+static void qcom_swrm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev);
+
+ sdw_release_stream(ctrl->sruntime[dai->id]);
+ ctrl->sruntime[dai->id] = NULL;
+ pm_runtime_mark_last_busy(ctrl->dev);
+ pm_runtime_put_autosuspend(ctrl->dev);
+
+}
+
+static const struct snd_soc_dai_ops qcom_swrm_pdm_dai_ops = {
+ .hw_params = qcom_swrm_hw_params,
+ .hw_free = qcom_swrm_hw_free,
+ .startup = qcom_swrm_startup,
+ .shutdown = qcom_swrm_shutdown,
+ .set_stream = qcom_swrm_set_sdw_stream,
+ .get_stream = qcom_swrm_get_sdw_stream,
+};
+
+static const struct snd_soc_component_driver qcom_swrm_dai_component = {
+ .name = "soundwire",
+};
+
+static int qcom_swrm_register_dais(struct qcom_swrm_ctrl *ctrl)
+{
+ int num_dais = ctrl->num_dout_ports + ctrl->num_din_ports;
+ struct snd_soc_dai_driver *dais;
+ struct snd_soc_pcm_stream *stream;
+ struct device *dev = ctrl->dev;
+ int i;
+
+ /* PDM dais are only tested for now */
+ dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL);
+ if (!dais)
+ return -ENOMEM;
+
+ for (i = 0; i < num_dais; i++) {
+ dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW Pin%d", i);
+ if (!dais[i].name)
+ return -ENOMEM;
+
+ if (i < ctrl->num_dout_ports)
+ stream = &dais[i].playback;
+ else
+ stream = &dais[i].capture;
+
+ stream->channels_min = 1;
+ stream->channels_max = 1;
+ stream->rates = SNDRV_PCM_RATE_48000;
+ stream->formats = SNDRV_PCM_FMTBIT_S16_LE;
+
+ dais[i].ops = &qcom_swrm_pdm_dai_ops;
+ dais[i].id = i;
+ }
+
+ return devm_snd_soc_register_component(ctrl->dev,
+ &qcom_swrm_dai_component,
+ dais, num_dais);
+}
+
+static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl)
+{
+ struct device_node *np = ctrl->dev->of_node;
+ 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, };
+ u8 hstart[QCOM_SDW_MAX_PORTS];
+ u8 hstop[QCOM_SDW_MAX_PORTS];
+ u8 word_length[QCOM_SDW_MAX_PORTS];
+ u8 blk_group_count[QCOM_SDW_MAX_PORTS];
+ u8 lane_control[QCOM_SDW_MAX_PORTS];
+ int i, ret, nports, val;
+
+ ctrl->reg_read(ctrl, SWRM_COMP_PARAMS, &val);
+
+ 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)
+ return ret;
+
+ if (val > ctrl->num_din_ports)
+ return -EINVAL;
+
+ ctrl->num_din_ports = val;
+
+ ret = of_property_read_u32(np, "qcom,dout-ports", &val);
+ if (ret)
+ return ret;
+
+ if (val > ctrl->num_dout_ports)
+ return -EINVAL;
+
+ ctrl->num_dout_ports = val;
+
+ nports = ctrl->num_dout_ports + ctrl->num_din_ports;
+ if (nports > QCOM_SDW_MAX_PORTS)
+ return -EINVAL;
+
+ /* Valid port numbers are from 1-14, so mask out port 0 explicitly */
+ set_bit(0, &ctrl->dout_port_mask);
+ set_bit(0, &ctrl->din_port_mask);
+
+ ret = of_property_read_u8_array(np, "qcom,ports-offset1",
+ off1, nports);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_u8_array(np, "qcom,ports-offset2",
+ off2, nports);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_u8_array(np, "qcom,ports-sinterval-low",
+ si, nports);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_u8_array(np, "qcom,ports-block-pack-mode",
+ bp_mode, nports);
+ if (ret) {
+ if (ctrl->version <= 0x01030000)
+ memset(bp_mode, SWR_INVALID_PARAM, QCOM_SDW_MAX_PORTS);
+ else
+ return ret;
+ }
+
+ memset(hstart, SWR_INVALID_PARAM, QCOM_SDW_MAX_PORTS);
+ of_property_read_u8_array(np, "qcom,ports-hstart", hstart, nports);
+
+ memset(hstop, SWR_INVALID_PARAM, QCOM_SDW_MAX_PORTS);
+ of_property_read_u8_array(np, "qcom,ports-hstop", hstop, nports);
+
+ memset(word_length, SWR_INVALID_PARAM, QCOM_SDW_MAX_PORTS);
+ of_property_read_u8_array(np, "qcom,ports-word-length", word_length, nports);
+
+ memset(blk_group_count, SWR_INVALID_PARAM, QCOM_SDW_MAX_PORTS);
+ of_property_read_u8_array(np, "qcom,ports-block-group-count", blk_group_count, nports);
+
+ memset(lane_control, SWR_INVALID_PARAM, QCOM_SDW_MAX_PORTS);
+ of_property_read_u8_array(np, "qcom,ports-lane-control", lane_control, nports);
+
+ for (i = 0; i < nports; i++) {
+ /* Valid port number range is from 1-14 */
+ ctrl->pconfig[i + 1].si = si[i];
+ ctrl->pconfig[i + 1].off1 = off1[i];
+ ctrl->pconfig[i + 1].off2 = off2[i];
+ ctrl->pconfig[i + 1].bp_mode = bp_mode[i];
+ ctrl->pconfig[i + 1].hstart = hstart[i];
+ ctrl->pconfig[i + 1].hstop = hstop[i];
+ ctrl->pconfig[i + 1].word_length = word_length[i];
+ ctrl->pconfig[i + 1].blk_group_count = blk_group_count[i];
+ ctrl->pconfig[i + 1].lane_control = lane_control[i];
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int swrm_reg_show(struct seq_file *s_file, void *data)
+{
+ struct qcom_swrm_ctrl *swrm = s_file->private;
+ int reg, reg_val, ret;
+
+ ret = pm_runtime_resume_and_get(swrm->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(swrm->dev,
+ "pm_runtime_resume_and_get failed in %s, ret %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ for (reg = 0; reg <= SWR_MSTR_MAX_REG_ADDR; reg += 4) {
+ swrm->reg_read(swrm, reg, &reg_val);
+ seq_printf(s_file, "0x%.3x: 0x%.2x\n", reg, reg_val);
+ }
+ pm_runtime_mark_last_busy(swrm->dev);
+ pm_runtime_put_autosuspend(swrm->dev);
+
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(swrm_reg);
+#endif
+
+static int qcom_swrm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ 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;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ 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_REACHABLE(CONFIG_SLIMBUS)
+ if (dev->parent->bus == &slimbus_bus) {
+#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 {
+ 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);
+ }
+
+ if (data->sw_clk_gate_required) {
+ ctrl->audio_cgcr = devm_reset_control_get_exclusive(dev, "swr_audio_cgcr");
+ if (IS_ERR_OR_NULL(ctrl->audio_cgcr)) {
+ dev_err(dev, "Failed to get cgcr reset ctrl required for SW gating\n");
+ ret = PTR_ERR(ctrl->audio_cgcr);
+ goto err_init;
+ }
+ }
+
+ ctrl->irq = of_irq_get(dev->of_node, 0);
+ if (ctrl->irq < 0) {
+ ret = ctrl->irq;
+ goto err_init;
+ }
+
+ ctrl->hclk = devm_clk_get(dev, "iface");
+ if (IS_ERR(ctrl->hclk)) {
+ ret = PTR_ERR(ctrl->hclk);
+ goto err_init;
+ }
+
+ clk_prepare_enable(ctrl->hclk);
+
+ ctrl->dev = dev;
+ dev_set_drvdata(&pdev->dev, ctrl);
+ mutex_init(&ctrl->port_lock);
+ init_completion(&ctrl->broadcast);
+ init_completion(&ctrl->enumeration);
+
+ ctrl->bus.ops = &qcom_swrm_ops;
+ ctrl->bus.port_ops = &qcom_swrm_port_ops;
+ ctrl->bus.compute_params = &qcom_swrm_compute_params;
+ ctrl->bus.clk_stop_timeout = 300;
+
+ ret = qcom_swrm_get_port_config(ctrl);
+ if (ret)
+ goto err_clk;
+
+ params = &ctrl->bus.params;
+ params->max_dr_freq = DEFAULT_CLK_FREQ;
+ params->curr_dr_freq = DEFAULT_CLK_FREQ;
+ 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;
+
+ prop = &ctrl->bus.prop;
+ prop->max_clk_freq = DEFAULT_CLK_FREQ;
+ prop->num_clk_gears = 0;
+ prop->num_clk_freq = MAX_FREQ_NUM;
+ prop->clk_freq = &qcom_swrm_freq_tbl[0];
+ prop->default_col = data->default_cols;
+ prop->default_row = data->default_rows;
+
+ ctrl->reg_read(ctrl, SWRM_COMP_HW_VERSION, &ctrl->version);
+
+ ret = devm_request_threaded_irq(dev, ctrl->irq, NULL,
+ qcom_swrm_irq_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT,
+ "soundwire", ctrl);
+ if (ret) {
+ dev_err(dev, "Failed to request soundwire irq\n");
+ goto err_clk;
+ }
+
+ ctrl->wake_irq = of_irq_get(dev->of_node, 1);
+ if (ctrl->wake_irq > 0) {
+ ret = devm_request_threaded_irq(dev, ctrl->wake_irq, NULL,
+ qcom_swrm_wake_irq_handler,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+ "swr_wake_irq", ctrl);
+ if (ret) {
+ dev_err(dev, "Failed to request soundwire wake irq\n");
+ goto err_init;
+ }
+ }
+
+ ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode);
+ if (ret) {
+ dev_err(dev, "Failed to register Soundwire controller (%d)\n",
+ ret);
+ goto err_clk;
+ }
+
+ qcom_swrm_init(ctrl);
+ wait_for_completion_timeout(&ctrl->enumeration,
+ msecs_to_jiffies(TIMEOUT_MS));
+ ret = qcom_swrm_register_dais(ctrl);
+ if (ret)
+ goto err_master_add;
+
+ dev_info(dev, "Qualcomm Soundwire controller v%x.%x.%x Registered\n",
+ (ctrl->version >> 24) & 0xff, (ctrl->version >> 16) & 0xff,
+ ctrl->version & 0xffff);
+
+ pm_runtime_set_autosuspend_delay(dev, 3000);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ /* Clk stop is not supported on WSA Soundwire masters */
+ if (ctrl->version <= 0x01030000) {
+ ctrl->clock_stop_not_supported = true;
+ } else {
+ ctrl->reg_read(ctrl, SWRM_COMP_MASTER_ID, &val);
+ if (val == MASTER_ID_WSA)
+ ctrl->clock_stop_not_supported = true;
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ ctrl->debugfs = debugfs_create_dir("qualcomm-sdw", ctrl->bus.debugfs);
+ debugfs_create_file("qualcomm-registers", 0400, ctrl->debugfs, ctrl,
+ &swrm_reg_fops);
+#endif
+
+ return 0;
+
+err_master_add:
+ sdw_bus_master_delete(&ctrl->bus);
+err_clk:
+ clk_disable_unprepare(ctrl->hclk);
+err_init:
+ return ret;
+}
+
+static int qcom_swrm_remove(struct platform_device *pdev)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(&pdev->dev);
+
+ sdw_bus_master_delete(&ctrl->bus);
+ clk_disable_unprepare(ctrl->hclk);
+
+ return 0;
+}
+
+static bool swrm_wait_for_frame_gen_enabled(struct qcom_swrm_ctrl *swrm)
+{
+ int retry = SWRM_LINK_STATUS_RETRY_CNT;
+ int comp_sts;
+
+ do {
+ swrm->reg_read(swrm, SWRM_COMP_STATUS, &comp_sts);
+
+ if (comp_sts & SWRM_FRM_GEN_ENABLED)
+ return true;
+
+ usleep_range(500, 510);
+ } while (retry--);
+
+ dev_err(swrm->dev, "%s: link status not %s\n", __func__,
+ comp_sts & SWRM_FRM_GEN_ENABLED ? "connected" : "disconnected");
+
+ return false;
+}
+
+static int __maybe_unused swrm_runtime_resume(struct device *dev)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dev);
+ int ret;
+
+ if (ctrl->wake_irq > 0) {
+ if (!irqd_irq_disabled(irq_get_irq_data(ctrl->wake_irq)))
+ disable_irq_nosync(ctrl->wake_irq);
+ }
+
+ clk_prepare_enable(ctrl->hclk);
+
+ if (ctrl->clock_stop_not_supported) {
+ reinit_completion(&ctrl->enumeration);
+ ctrl->reg_write(ctrl, SWRM_COMP_SW_RESET, 0x01);
+ usleep_range(100, 105);
+
+ qcom_swrm_init(ctrl);
+
+ usleep_range(100, 105);
+ if (!swrm_wait_for_frame_gen_enabled(ctrl))
+ dev_err(ctrl->dev, "link failed to connect\n");
+
+ /* wait for hw enumeration to complete */
+ wait_for_completion_timeout(&ctrl->enumeration,
+ msecs_to_jiffies(TIMEOUT_MS));
+ qcom_swrm_get_device_status(ctrl);
+ sdw_handle_slave_status(&ctrl->bus, ctrl->status);
+ } else {
+ reset_control_reset(ctrl->audio_cgcr);
+
+ ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START);
+ ctrl->reg_write(ctrl, SWRM_INTERRUPT_CLEAR,
+ SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET);
+
+ ctrl->intr_mask |= SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
+ ctrl->reg_write(ctrl, SWRM_INTERRUPT_MASK_ADDR, ctrl->intr_mask);
+ ctrl->reg_write(ctrl, SWRM_INTERRUPT_CPU_EN, ctrl->intr_mask);
+
+ usleep_range(100, 105);
+ if (!swrm_wait_for_frame_gen_enabled(ctrl))
+ dev_err(ctrl->dev, "link failed to connect\n");
+
+ ret = sdw_bus_exit_clk_stop(&ctrl->bus);
+ if (ret < 0)
+ dev_err(ctrl->dev, "bus failed to exit clock stop %d\n", ret);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused swrm_runtime_suspend(struct device *dev)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dev);
+ int ret;
+
+ if (!ctrl->clock_stop_not_supported) {
+ /* Mask bus clash interrupt */
+ ctrl->intr_mask &= ~SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
+ ctrl->reg_write(ctrl, SWRM_INTERRUPT_MASK_ADDR, ctrl->intr_mask);
+ ctrl->reg_write(ctrl, SWRM_INTERRUPT_CPU_EN, ctrl->intr_mask);
+ /* Prepare slaves for clock stop */
+ ret = sdw_bus_prep_clk_stop(&ctrl->bus);
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(dev, "prepare clock stop failed %d", ret);
+ return ret;
+ }
+
+ ret = sdw_bus_clk_stop(&ctrl->bus);
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(dev, "bus clock stop failed %d", ret);
+ return ret;
+ }
+ }
+
+ clk_disable_unprepare(ctrl->hclk);
+
+ usleep_range(300, 305);
+
+ if (ctrl->wake_irq > 0) {
+ if (irqd_irq_disabled(irq_get_irq_data(ctrl->wake_irq)))
+ enable_irq(ctrl->wake_irq);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops swrm_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(swrm_runtime_suspend, swrm_runtime_resume, NULL)
+};
+
+static const struct of_device_id qcom_swrm_of_match[] = {
+ { .compatible = "qcom,soundwire-v1.3.0", .data = &swrm_v1_3_data },
+ { .compatible = "qcom,soundwire-v1.5.1", .data = &swrm_v1_5_data },
+ { .compatible = "qcom,soundwire-v1.6.0", .data = &swrm_v1_6_data },
+ {/* sentinel */},
+};
+
+MODULE_DEVICE_TABLE(of, qcom_swrm_of_match);
+
+static struct platform_driver qcom_swrm_driver = {
+ .probe = &qcom_swrm_probe,
+ .remove = &qcom_swrm_remove,
+ .driver = {
+ .name = "qcom-soundwire",
+ .of_match_table = qcom_swrm_of_match,
+ .pm = &swrm_dev_pm_ops,
+ }
+};
+module_platform_driver(qcom_swrm_driver);
+
+MODULE_DESCRIPTION("Qualcomm soundwire driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c
new file mode 100644
index 000000000..c1c1a2ac2
--- /dev/null
+++ b/drivers/soundwire/slave.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+#include <linux/acpi.h>
+#include <linux/of.h>
+#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)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+
+ mutex_destroy(&slave->sdw_dev_lock);
+ kfree(slave);
+}
+
+struct device_type sdw_slave_type = {
+ .name = "sdw_slave",
+ .release = sdw_slave_release,
+ .uevent = sdw_slave_uevent,
+};
+
+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)
+ return -ENOMEM;
+
+ /* Initialize data structure */
+ memcpy(&slave->id, id, sizeof(*id));
+ slave->dev.parent = bus->dev;
+ slave->dev.fwnode = fwnode;
+
+ if (id->unique_id == SDW_IGNORED_UNIQUE_ID) {
+ /* name shall be sdw:link:mfg:part:class */
+ dev_set_name(&slave->dev, "sdw:%01x:%04x:%04x:%02x",
+ bus->link_id, id->mfg_id, id->part_id,
+ id->class_id);
+ } else {
+ /* name shall be sdw:link:mfg:part:class:unique */
+ dev_set_name(&slave->dev, "sdw:%01x:%04x:%04x:%02x:%01x",
+ bus->link_id, id->mfg_id, id->part_id,
+ id->class_id, id->unique_id);
+ }
+
+ 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);
+ init_completion(&slave->initialization_complete);
+ slave->dev_num = 0;
+ slave->probed = false;
+ slave->first_interrupt_done = false;
+ mutex_init(&slave->sdw_dev_lock);
+
+ 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);
+ mutex_unlock(&bus->bus_lock);
+
+ ret = device_register(&slave->dev);
+ if (ret) {
+ dev_err(bus->dev, "Failed to add slave: ret %d\n", ret);
+
+ /*
+ * On err, don't free but drop ref as this will be freed
+ * when release method is invoked.
+ */
+ mutex_lock(&bus->bus_lock);
+ list_del(&slave->node);
+ mutex_unlock(&bus->bus_lock);
+ put_device(&slave->dev);
+
+ return ret;
+ }
+ sdw_slave_debugfs_init(slave);
+
+ return ret;
+}
+EXPORT_SYMBOL(sdw_slave_add);
+
+#if IS_ENABLED(CONFIG_ACPI)
+
+static bool find_slave(struct sdw_bus *bus,
+ struct acpi_device *adev,
+ struct sdw_slave_id *id)
+{
+ u64 addr;
+ unsigned int link_id;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(adev->handle,
+ METHOD_NAME__ADR, NULL, &addr);
+
+ if (ACPI_FAILURE(status)) {
+ dev_err(bus->dev, "_ADR resolution failed: %x\n",
+ status);
+ return false;
+ }
+
+ if (bus->ops->override_adr)
+ addr = bus->ops->override_adr(bus, addr);
+
+ if (!addr)
+ return false;
+
+ /* Extract link id from ADR, Bit 51 to 48 (included) */
+ link_id = SDW_DISCO_LINK_ID(addr);
+
+ /* Check for link_id match */
+ if (link_id != bus->link_id)
+ return false;
+
+ sdw_extract_slave_id(bus, addr, id);
+
+ return true;
+}
+
+struct sdw_acpi_child_walk_data {
+ struct sdw_bus *bus;
+ struct acpi_device *adev;
+ struct sdw_slave_id id;
+ bool ignore_unique_id;
+};
+
+static int sdw_acpi_check_duplicate(struct acpi_device *adev, void *data)
+{
+ struct sdw_acpi_child_walk_data *cwd = data;
+ struct sdw_bus *bus = cwd->bus;
+ struct sdw_slave_id id;
+
+ if (adev == cwd->adev)
+ return 0;
+
+ if (!find_slave(bus, adev, &id))
+ return 0;
+
+ if (cwd->id.sdw_version != id.sdw_version || cwd->id.mfg_id != id.mfg_id ||
+ cwd->id.part_id != id.part_id || cwd->id.class_id != id.class_id)
+ return 0;
+
+ if (cwd->id.unique_id != id.unique_id) {
+ dev_dbg(bus->dev,
+ "Valid unique IDs 0x%x 0x%x for Slave mfg_id 0x%04x, part_id 0x%04x\n",
+ cwd->id.unique_id, id.unique_id, cwd->id.mfg_id,
+ cwd->id.part_id);
+ cwd->ignore_unique_id = false;
+ return 0;
+ }
+
+ dev_err(bus->dev,
+ "Invalid unique IDs 0x%x 0x%x for Slave mfg_id 0x%04x, part_id 0x%04x\n",
+ cwd->id.unique_id, id.unique_id, cwd->id.mfg_id, cwd->id.part_id);
+ return -ENODEV;
+}
+
+static int sdw_acpi_find_one(struct acpi_device *adev, void *data)
+{
+ struct sdw_bus *bus = data;
+ struct sdw_acpi_child_walk_data cwd = {
+ .bus = bus,
+ .adev = adev,
+ .ignore_unique_id = true,
+ };
+ int ret;
+
+ if (!find_slave(bus, adev, &cwd.id))
+ return 0;
+
+ /* Brute-force O(N^2) search for duplicates. */
+ ret = acpi_dev_for_each_child(ACPI_COMPANION(bus->dev),
+ sdw_acpi_check_duplicate, &cwd);
+ if (ret)
+ return ret;
+
+ if (cwd.ignore_unique_id)
+ cwd.id.unique_id = SDW_IGNORED_UNIQUE_ID;
+
+ /* Ignore errors and continue. */
+ sdw_slave_add(bus, &cwd.id, acpi_fwnode_handle(adev));
+ return 0;
+}
+
+/*
+ * sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node
+ * @bus: SDW bus instance
+ *
+ * Scans Master ACPI node for SDW child Slave devices and registers it.
+ */
+int sdw_acpi_find_slaves(struct sdw_bus *bus)
+{
+ struct acpi_device *parent;
+
+ parent = ACPI_COMPANION(bus->dev);
+ if (!parent) {
+ dev_err(bus->dev, "Can't find parent for acpi bind\n");
+ return -ENODEV;
+ }
+
+ return acpi_dev_for_each_child(parent, sdw_acpi_find_one, bus);
+}
+
+#endif
+
+/*
+ * sdw_of_find_slaves() - Find Slave devices in master device tree node
+ * @bus: SDW bus instance
+ *
+ * Scans Master DT node for SDW child Slave devices and registers it.
+ */
+int sdw_of_find_slaves(struct sdw_bus *bus)
+{
+ struct device *dev = bus->dev;
+ struct device_node *node;
+
+ for_each_child_of_node(bus->dev->of_node, node) {
+ int link_id, ret, len;
+ unsigned int sdw_version;
+ const char *compat = NULL;
+ struct sdw_slave_id id;
+ const __be32 *addr;
+
+ compat = of_get_property(node, "compatible", NULL);
+ if (!compat)
+ continue;
+
+ ret = sscanf(compat, "sdw%01x%04hx%04hx%02hhx", &sdw_version,
+ &id.mfg_id, &id.part_id, &id.class_id);
+
+ if (ret != 4) {
+ dev_err(dev, "Invalid compatible string found %s\n",
+ compat);
+ continue;
+ }
+
+ addr = of_get_property(node, "reg", &len);
+ if (!addr || (len < 2 * sizeof(u32))) {
+ dev_err(dev, "Invalid Link and Instance ID\n");
+ continue;
+ }
+
+ link_id = be32_to_cpup(addr++);
+ id.unique_id = be32_to_cpup(addr);
+ id.sdw_version = sdw_version;
+
+ /* Check for link_id match */
+ if (link_id != bus->link_id)
+ continue;
+
+ sdw_slave_add(bus, &id, of_fwnode_handle(node));
+ }
+
+ return 0;
+}
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c
new file mode 100644
index 000000000..2624441d2
--- /dev/null
+++ b/drivers/soundwire/stream.c
@@ -0,0 +1,2106 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-18 Intel Corporation.
+
+/*
+ * stream.c - SoundWire Bus stream operations.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/soc.h>
+#include "bus.h"
+
+/*
+ * Array of supported rows and columns as per MIPI SoundWire Specification 1.1
+ *
+ * The rows are arranged as per the array index value programmed
+ * in register. The index 15 has dummy value 0 in order to fill hole.
+ */
+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)
+{
+ int i;
+
+ for (i = 0; i < SDW_FRAME_COLS; i++) {
+ if (sdw_cols[i] == col)
+ return i;
+ }
+
+ pr_warn("Requested column not found, selecting lowest column no: 2\n");
+ return 0;
+}
+EXPORT_SYMBOL(sdw_find_col_index);
+
+int sdw_find_row_index(int row)
+{
+ int i;
+
+ for (i = 0; i < SDW_FRAME_ROWS; i++) {
+ if (sdw_rows[i] == row)
+ return i;
+ }
+
+ pr_warn("Requested row not found, selecting lowest row no: 48\n");
+ return 0;
+}
+EXPORT_SYMBOL(sdw_find_row_index);
+
+static int _sdw_program_slave_port_params(struct sdw_bus *bus,
+ struct sdw_slave *slave,
+ struct sdw_transport_params *t_params,
+ enum sdw_dpn_type type)
+{
+ u32 addr1, addr2, addr3, addr4;
+ int ret;
+ u16 wbuf;
+
+ if (bus->params.next_bank) {
+ addr1 = SDW_DPN_OFFSETCTRL2_B1(t_params->port_num);
+ addr2 = SDW_DPN_BLOCKCTRL3_B1(t_params->port_num);
+ addr3 = SDW_DPN_SAMPLECTRL2_B1(t_params->port_num);
+ addr4 = SDW_DPN_HCTRL_B1(t_params->port_num);
+ } else {
+ addr1 = SDW_DPN_OFFSETCTRL2_B0(t_params->port_num);
+ addr2 = SDW_DPN_BLOCKCTRL3_B0(t_params->port_num);
+ addr3 = SDW_DPN_SAMPLECTRL2_B0(t_params->port_num);
+ addr4 = SDW_DPN_HCTRL_B0(t_params->port_num);
+ }
+
+ /* Program DPN_OffsetCtrl2 registers */
+ ret = sdw_write(slave, addr1, t_params->offset2);
+ if (ret < 0) {
+ dev_err(bus->dev, "DPN_OffsetCtrl2 register write failed\n");
+ return ret;
+ }
+
+ /* Program DPN_BlockCtrl3 register */
+ ret = sdw_write(slave, addr2, t_params->blk_pkg_mode);
+ if (ret < 0) {
+ dev_err(bus->dev, "DPN_BlockCtrl3 register write failed\n");
+ return ret;
+ }
+
+ /*
+ * Data ports are FULL, SIMPLE and REDUCED. This function handles
+ * FULL and REDUCED only and beyond this point only FULL is
+ * handled, so bail out if we are not FULL data port type
+ */
+ if (type != SDW_DPN_FULL)
+ return ret;
+
+ /* Program DPN_SampleCtrl2 register */
+ wbuf = FIELD_GET(SDW_DPN_SAMPLECTRL_HIGH, t_params->sample_interval - 1);
+
+ ret = sdw_write(slave, addr3, wbuf);
+ if (ret < 0) {
+ dev_err(bus->dev, "DPN_SampleCtrl2 register write failed\n");
+ return ret;
+ }
+
+ /* Program DPN_HCtrl register */
+ 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)
+ dev_err(bus->dev, "DPN_HCtrl register write failed\n");
+
+ return ret;
+}
+
+static int sdw_program_slave_port_params(struct sdw_bus *bus,
+ struct sdw_slave_runtime *s_rt,
+ struct sdw_port_runtime *p_rt)
+{
+ struct sdw_transport_params *t_params = &p_rt->transport_params;
+ struct sdw_port_params *p_params = &p_rt->port_params;
+ struct sdw_slave_prop *slave_prop = &s_rt->slave->prop;
+ u32 addr1, addr2, addr3, addr4, addr5, addr6;
+ struct sdw_dpn_prop *dpn_prop;
+ int ret;
+ u8 wbuf;
+
+ if (s_rt->slave->is_mockup_device)
+ return 0;
+
+ dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave,
+ s_rt->direction,
+ t_params->port_num);
+ if (!dpn_prop)
+ return -EINVAL;
+
+ addr1 = SDW_DPN_PORTCTRL(t_params->port_num);
+ addr2 = SDW_DPN_BLOCKCTRL1(t_params->port_num);
+
+ if (bus->params.next_bank) {
+ addr3 = SDW_DPN_SAMPLECTRL1_B1(t_params->port_num);
+ addr4 = SDW_DPN_OFFSETCTRL1_B1(t_params->port_num);
+ addr5 = SDW_DPN_BLOCKCTRL2_B1(t_params->port_num);
+ addr6 = SDW_DPN_LANECTRL_B1(t_params->port_num);
+
+ } else {
+ addr3 = SDW_DPN_SAMPLECTRL1_B0(t_params->port_num);
+ addr4 = SDW_DPN_OFFSETCTRL1_B0(t_params->port_num);
+ addr5 = SDW_DPN_BLOCKCTRL2_B0(t_params->port_num);
+ addr6 = SDW_DPN_LANECTRL_B0(t_params->port_num);
+ }
+
+ /* Program DPN_PortCtrl register */
+ 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) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_PortCtrl register write failed for port %d\n",
+ t_params->port_num);
+ return ret;
+ }
+
+ if (!dpn_prop->read_only_wordlength) {
+ /* Program DPN_BlockCtrl1 register */
+ ret = sdw_write(s_rt->slave, addr2, (p_params->bps - 1));
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_BlockCtrl1 register write failed for port %d\n",
+ t_params->port_num);
+ return ret;
+ }
+ }
+
+ /* Program DPN_SampleCtrl1 register */
+ wbuf = (t_params->sample_interval - 1) & SDW_DPN_SAMPLECTRL_LOW;
+ ret = sdw_write(s_rt->slave, addr3, wbuf);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_SampleCtrl1 register write failed for port %d\n",
+ t_params->port_num);
+ return ret;
+ }
+
+ /* Program DPN_OffsetCtrl1 registers */
+ ret = sdw_write(s_rt->slave, addr4, t_params->offset1);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_OffsetCtrl1 register write failed for port %d\n",
+ t_params->port_num);
+ return ret;
+ }
+
+ /* Program DPN_BlockCtrl2 register*/
+ if (t_params->blk_grp_ctrl_valid) {
+ ret = sdw_write(s_rt->slave, addr5, t_params->blk_grp_ctrl);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_BlockCtrl2 reg write failed for port %d\n",
+ t_params->port_num);
+ return ret;
+ }
+ }
+
+ /* program DPN_LaneCtrl register */
+ if (slave_prop->lane_control_support) {
+ ret = sdw_write(s_rt->slave, addr6, t_params->lane_ctrl);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_LaneCtrl register write failed for port %d\n",
+ t_params->port_num);
+ return ret;
+ }
+ }
+
+ if (dpn_prop->type != SDW_DPN_SIMPLE) {
+ ret = _sdw_program_slave_port_params(bus, s_rt->slave,
+ t_params, dpn_prop->type);
+ if (ret < 0)
+ dev_err(&s_rt->slave->dev,
+ "Transport reg write failed for port: %d\n",
+ t_params->port_num);
+ }
+
+ return ret;
+}
+
+static int sdw_program_master_port_params(struct sdw_bus *bus,
+ struct sdw_port_runtime *p_rt)
+{
+ int ret;
+
+ /*
+ * we need to set transport and port parameters for the port.
+ * Transport parameters refers to the sample interval, offsets and
+ * hstart/stop etc of the data. Port parameters refers to word
+ * length, flow mode etc of the port
+ */
+ ret = bus->port_ops->dpn_set_port_transport_params(bus,
+ &p_rt->transport_params,
+ bus->params.next_bank);
+ if (ret < 0)
+ return ret;
+
+ return bus->port_ops->dpn_set_port_params(bus,
+ &p_rt->port_params,
+ bus->params.next_bank);
+}
+
+/**
+ * sdw_program_port_params() - Programs transport parameters of Master(s)
+ * and Slave(s)
+ *
+ * @m_rt: Master stream runtime
+ */
+static int sdw_program_port_params(struct sdw_master_runtime *m_rt)
+{
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_port_runtime *p_rt;
+ int ret = 0;
+
+ /* Program transport & port parameters for Slave(s) */
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ ret = sdw_program_slave_port_params(bus, s_rt, p_rt);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* Program transport & port parameters for Master(s) */
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ ret = sdw_program_master_port_params(bus, p_rt);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_enable_disable_slave_ports: Enable/disable slave data port
+ *
+ * @bus: bus instance
+ * @s_rt: slave runtime
+ * @p_rt: port runtime
+ * @en: enable or disable operation
+ *
+ * This function only sets the enable/disable bits in the relevant bank, the
+ * actual enable/disable is done with a bank switch
+ */
+static int sdw_enable_disable_slave_ports(struct sdw_bus *bus,
+ struct sdw_slave_runtime *s_rt,
+ struct sdw_port_runtime *p_rt,
+ bool en)
+{
+ struct sdw_transport_params *t_params = &p_rt->transport_params;
+ u32 addr;
+ int ret;
+
+ if (bus->params.next_bank)
+ addr = SDW_DPN_CHANNELEN_B1(p_rt->num);
+ else
+ addr = SDW_DPN_CHANNELEN_B0(p_rt->num);
+
+ /*
+ * Since bus doesn't support sharing a port across two streams,
+ * it is safe to reset this register
+ */
+ if (en)
+ ret = sdw_write(s_rt->slave, addr, p_rt->ch_mask);
+ else
+ ret = sdw_write(s_rt->slave, addr, 0x0);
+
+ if (ret < 0)
+ dev_err(&s_rt->slave->dev,
+ "Slave chn_en reg write failed:%d port:%d\n",
+ ret, t_params->port_num);
+
+ return ret;
+}
+
+static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt,
+ struct sdw_port_runtime *p_rt,
+ bool en)
+{
+ struct sdw_transport_params *t_params = &p_rt->transport_params;
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_enable_ch enable_ch;
+ int ret;
+
+ enable_ch.port_num = p_rt->num;
+ enable_ch.ch_mask = p_rt->ch_mask;
+ enable_ch.enable = en;
+
+ /* Perform Master port channel(s) enable/disable */
+ if (bus->port_ops->dpn_port_enable_ch) {
+ ret = bus->port_ops->dpn_port_enable_ch(bus,
+ &enable_ch,
+ bus->params.next_bank);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Master chn_en write failed:%d port:%d\n",
+ ret, t_params->port_num);
+ return ret;
+ }
+ } else {
+ dev_err(bus->dev,
+ "dpn_port_enable_ch not supported, %s failed\n",
+ en ? "enable" : "disable");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_enable_disable_ports() - Enable/disable port(s) for Master and
+ * Slave(s)
+ *
+ * @m_rt: Master stream runtime
+ * @en: mode (enable/disable)
+ */
+static int sdw_enable_disable_ports(struct sdw_master_runtime *m_rt, bool en)
+{
+ struct sdw_port_runtime *s_port, *m_port;
+ struct sdw_slave_runtime *s_rt;
+ int ret = 0;
+
+ /* Enable/Disable Slave port(s) */
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ list_for_each_entry(s_port, &s_rt->port_list, port_node) {
+ ret = sdw_enable_disable_slave_ports(m_rt->bus, s_rt,
+ s_port, en);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* Enable/Disable Master port(s) */
+ list_for_each_entry(m_port, &m_rt->port_list, port_node) {
+ ret = sdw_enable_disable_master_ports(m_rt, m_port, en);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sdw_do_port_prep(struct sdw_slave_runtime *s_rt,
+ struct sdw_prepare_ch prep_ch,
+ enum sdw_port_prep_ops cmd)
+{
+ int ret = 0;
+ struct sdw_slave *slave = s_rt->slave;
+
+ mutex_lock(&slave->sdw_dev_lock);
+
+ if (slave->probed) {
+ struct device *dev = &slave->dev;
+ struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
+
+ if (drv->ops && drv->ops->port_prep) {
+ ret = drv->ops->port_prep(slave, &prep_ch, cmd);
+ if (ret < 0)
+ dev_err(dev, "Slave Port Prep cmd %d failed: %d\n",
+ cmd, ret);
+ }
+ }
+
+ mutex_unlock(&slave->sdw_dev_lock);
+
+ return ret;
+}
+
+static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
+ struct sdw_slave_runtime *s_rt,
+ struct sdw_port_runtime *p_rt,
+ bool prep)
+{
+ struct completion *port_ready;
+ struct sdw_dpn_prop *dpn_prop;
+ struct sdw_prepare_ch prep_ch;
+ bool intr = false;
+ int ret = 0, val;
+ u32 addr;
+
+ prep_ch.num = p_rt->num;
+ prep_ch.ch_mask = p_rt->ch_mask;
+
+ dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave,
+ s_rt->direction,
+ prep_ch.num);
+ if (!dpn_prop) {
+ dev_err(bus->dev,
+ "Slave Port:%d properties not found\n", prep_ch.num);
+ return -EINVAL;
+ }
+
+ prep_ch.prepare = prep;
+
+ prep_ch.bank = bus->params.next_bank;
+
+ if (dpn_prop->imp_def_interrupts || !dpn_prop->simple_ch_prep_sm ||
+ bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL)
+ intr = true;
+
+ /*
+ * Enable interrupt before Port prepare.
+ * For Port de-prepare, it is assumed that port
+ * was prepared earlier
+ */
+ if (prep && intr) {
+ ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
+ dpn_prop->imp_def_interrupts);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Inform slave about the impending port prepare */
+ sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_PRE_PREP);
+
+ /* Prepare Slave port implementing CP_SM */
+ if (!dpn_prop->simple_ch_prep_sm) {
+ addr = SDW_DPN_PREPARECTRL(p_rt->num);
+
+ if (prep)
+ ret = sdw_write(s_rt->slave, addr, p_rt->ch_mask);
+ else
+ ret = sdw_write(s_rt->slave, addr, 0x0);
+
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "Slave prep_ctrl reg write failed\n");
+ return ret;
+ }
+
+ /* Wait for completion on port ready */
+ port_ready = &s_rt->slave->port_ready[prep_ch.num];
+ wait_for_completion_timeout(port_ready,
+ msecs_to_jiffies(dpn_prop->ch_prep_timeout));
+
+ val = sdw_read(s_rt->slave, SDW_DPN_PREPARESTATUS(p_rt->num));
+ if ((val < 0) || (val & p_rt->ch_mask)) {
+ ret = (val < 0) ? val : -ETIMEDOUT;
+ dev_err(&s_rt->slave->dev,
+ "Chn prep failed for port %d: %d\n", prep_ch.num, ret);
+ return ret;
+ }
+ }
+
+ /* Inform slaves about ports prepared */
+ sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_POST_PREP);
+
+ /* Disable interrupt after Port de-prepare */
+ if (!prep && intr)
+ ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
+ dpn_prop->imp_def_interrupts);
+
+ return ret;
+}
+
+static int sdw_prep_deprep_master_ports(struct sdw_master_runtime *m_rt,
+ struct sdw_port_runtime *p_rt,
+ bool prep)
+{
+ struct sdw_transport_params *t_params = &p_rt->transport_params;
+ struct sdw_bus *bus = m_rt->bus;
+ const struct sdw_master_port_ops *ops = bus->port_ops;
+ struct sdw_prepare_ch prep_ch;
+ int ret = 0;
+
+ prep_ch.num = p_rt->num;
+ prep_ch.ch_mask = p_rt->ch_mask;
+ prep_ch.prepare = prep; /* Prepare/De-prepare */
+ prep_ch.bank = bus->params.next_bank;
+
+ /* Pre-prepare/Pre-deprepare port(s) */
+ if (ops->dpn_port_prep) {
+ ret = ops->dpn_port_prep(bus, &prep_ch);
+ if (ret < 0) {
+ dev_err(bus->dev, "Port prepare failed for port:%d\n",
+ t_params->port_num);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_prep_deprep_ports() - Prepare/De-prepare port(s) for Master(s) and
+ * Slave(s)
+ *
+ * @m_rt: Master runtime handle
+ * @prep: Prepare or De-prepare
+ */
+static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep)
+{
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_port_runtime *p_rt;
+ int ret = 0;
+
+ /* Prepare/De-prepare Slave port(s) */
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ ret = sdw_prep_deprep_slave_ports(m_rt->bus, s_rt,
+ p_rt, prep);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* Prepare/De-prepare Master port(s) */
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ ret = sdw_prep_deprep_master_ports(m_rt, p_rt, prep);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_notify_config() - Notify bus configuration
+ *
+ * @m_rt: Master runtime handle
+ *
+ * This function notifies the Master(s) and Slave(s) of the
+ * new bus configuration.
+ */
+static int sdw_notify_config(struct sdw_master_runtime *m_rt)
+{
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_slave *slave;
+ int ret;
+
+ if (bus->ops->set_bus_conf) {
+ ret = bus->ops->set_bus_conf(bus, &bus->params);
+ if (ret < 0)
+ return ret;
+ }
+
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ slave = s_rt->slave;
+
+ mutex_lock(&slave->sdw_dev_lock);
+
+ if (slave->probed) {
+ struct device *dev = &slave->dev;
+ struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
+
+ if (drv->ops && drv->ops->bus_config) {
+ ret = drv->ops->bus_config(slave, &bus->params);
+ if (ret < 0) {
+ dev_err(dev, "Notify Slave: %d failed\n",
+ slave->dev_num);
+ mutex_unlock(&slave->sdw_dev_lock);
+ return ret;
+ }
+ }
+ }
+
+ mutex_unlock(&slave->sdw_dev_lock);
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_program_params() - Program transport and port parameters for Master(s)
+ * and Slave(s)
+ *
+ * @bus: SDW bus instance
+ * @prepare: true if sdw_program_params() is called by _prepare.
+ */
+static int sdw_program_params(struct sdw_bus *bus, bool prepare)
+{
+ struct sdw_master_runtime *m_rt;
+ int ret = 0;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+
+ /*
+ * this loop walks through all master runtimes for a
+ * bus, but the ports can only be configured while
+ * explicitly preparing a stream or handling an
+ * already-prepared stream otherwise.
+ */
+ if (!prepare &&
+ m_rt->stream->state == SDW_STREAM_CONFIGURED)
+ continue;
+
+ ret = sdw_program_port_params(m_rt);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Program transport params failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = sdw_notify_config(m_rt);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Notify bus config failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Enable port(s) on alternate bank for all active streams */
+ if (m_rt->stream->state != SDW_STREAM_ENABLED)
+ continue;
+
+ ret = sdw_enable_disable_ports(m_rt, true);
+ if (ret < 0) {
+ dev_err(bus->dev, "Enable channel failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static int sdw_bank_switch(struct sdw_bus *bus, int m_rt_count)
+{
+ int col_index, row_index;
+ bool multi_link;
+ struct sdw_msg *wr_msg;
+ u8 *wbuf;
+ int ret;
+ u16 addr;
+
+ wr_msg = kzalloc(sizeof(*wr_msg), GFP_KERNEL);
+ if (!wr_msg)
+ return -ENOMEM;
+
+ bus->defer_msg.msg = wr_msg;
+
+ wbuf = kzalloc(sizeof(*wbuf), GFP_KERNEL);
+ if (!wbuf) {
+ ret = -ENOMEM;
+ goto error_1;
+ }
+
+ /* Get row and column index to program register */
+ col_index = sdw_find_col_index(bus->params.col);
+ row_index = sdw_find_row_index(bus->params.row);
+ wbuf[0] = col_index | (row_index << 3);
+
+ if (bus->params.next_bank)
+ addr = SDW_SCP_FRAMECTRL_B1;
+ else
+ addr = SDW_SCP_FRAMECTRL_B0;
+
+ sdw_fill_msg(wr_msg, NULL, addr, 1, SDW_BROADCAST_DEV_NUM,
+ SDW_MSG_FLAG_WRITE, wbuf);
+ wr_msg->ssp_sync = true;
+
+ /*
+ * Set the multi_link flag only when both the hardware supports
+ * and hardware-based sync is required
+ */
+ 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);
+ else
+ ret = sdw_transfer(bus, wr_msg);
+
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(bus->dev, "Slave frame_ctrl reg write failed\n");
+ goto error;
+ }
+
+ if (!multi_link) {
+ kfree(wr_msg);
+ kfree(wbuf);
+ bus->defer_msg.msg = NULL;
+ bus->params.curr_bank = !bus->params.curr_bank;
+ bus->params.next_bank = !bus->params.next_bank;
+ }
+
+ return 0;
+
+error:
+ kfree(wbuf);
+error_1:
+ kfree(wr_msg);
+ bus->defer_msg.msg = NULL;
+ return ret;
+}
+
+/**
+ * sdw_ml_sync_bank_switch: Multilink register bank switch
+ *
+ * @bus: SDW bus instance
+ * @multi_link: whether this is a multi-link stream with hardware-based sync
+ *
+ * Caller function should free the buffers on error
+ */
+static int sdw_ml_sync_bank_switch(struct sdw_bus *bus, bool multi_link)
+{
+ unsigned long time_left;
+
+ if (!multi_link)
+ return 0;
+
+ /* Wait for completion of transfer */
+ time_left = wait_for_completion_timeout(&bus->defer_msg.complete,
+ bus->bank_switch_timeout);
+
+ if (!time_left) {
+ dev_err(bus->dev, "Controller Timed out on bank switch\n");
+ return -ETIMEDOUT;
+ }
+
+ bus->params.curr_bank = !bus->params.curr_bank;
+ bus->params.next_bank = !bus->params.next_bank;
+
+ if (bus->defer_msg.msg) {
+ kfree(bus->defer_msg.msg->buf);
+ kfree(bus->defer_msg.msg);
+ }
+
+ return 0;
+}
+
+static int do_bank_switch(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+ 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 && m_rt_count >= bus->hw_sync_min_links) {
+ multi_link = true;
+ mutex_lock(&bus->msg_lock);
+ }
+
+ /* Pre-bank switch */
+ if (ops->pre_bank_switch) {
+ ret = ops->pre_bank_switch(bus);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Pre bank switch op failed: %d\n", ret);
+ goto msg_unlock;
+ }
+ }
+
+ /*
+ * Perform Bank switch operation.
+ * For multi link cases, the actual bank switch is
+ * synchronized across all Masters and happens later as a
+ * part of post_bank_switch ops.
+ */
+ ret = sdw_bank_switch(bus, m_rt_count);
+ if (ret < 0) {
+ dev_err(bus->dev, "Bank switch failed: %d\n", ret);
+ goto error;
+ }
+ }
+
+ /*
+ * For multi link cases, it is expected that the bank switch is
+ * triggered by the post_bank_switch for the first Master in the list
+ * and for the other Masters the post_bank_switch() should return doing
+ * nothing.
+ */
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+ ops = bus->ops;
+
+ /* Post-bank switch */
+ if (ops->post_bank_switch) {
+ ret = ops->post_bank_switch(bus);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Post bank switch op failed: %d\n",
+ ret);
+ goto error;
+ }
+ } else if (multi_link) {
+ dev_err(bus->dev,
+ "Post bank switch ops not implemented\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Set the bank switch timeout to default, if not set */
+ if (!bus->bank_switch_timeout)
+ bus->bank_switch_timeout = DEFAULT_BANK_SWITCH_TIMEOUT;
+
+ /* Check if bank switch was successful */
+ ret = sdw_ml_sync_bank_switch(bus, multi_link);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "multi link bank switch failed: %d\n", ret);
+ goto error;
+ }
+
+ if (multi_link)
+ mutex_unlock(&bus->msg_lock);
+ }
+
+ return ret;
+
+error:
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+ if (bus->defer_msg.msg) {
+ kfree(bus->defer_msg.msg->buf);
+ kfree(bus->defer_msg.msg);
+ }
+ }
+
+msg_unlock:
+
+ if (multi_link) {
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+ if (mutex_is_locked(&bus->msg_lock))
+ mutex_unlock(&bus->msg_lock);
+ }
+ }
+
+ return ret;
+}
+
+static struct sdw_port_runtime *sdw_port_alloc(struct list_head *port_list)
+{
+ struct sdw_port_runtime *p_rt;
+
+ p_rt = kzalloc(sizeof(*p_rt), GFP_KERNEL);
+ if (!p_rt)
+ return NULL;
+
+ list_add_tail(&p_rt->port_node, port_list);
+
+ return p_rt;
+}
+
+static int sdw_port_config(struct sdw_port_runtime *p_rt,
+ struct sdw_port_config *port_config,
+ int port_index)
+{
+ p_rt->ch_mask = port_config[port_index].ch_mask;
+ p_rt->num = port_config[port_index].num;
+
+ /*
+ * TODO: Check port capabilities for requested configuration
+ */
+
+ return 0;
+}
+
+static void sdw_port_free(struct sdw_port_runtime *p_rt)
+{
+ list_del(&p_rt->port_node);
+ kfree(p_rt);
+}
+
+static bool sdw_slave_port_allocated(struct sdw_slave_runtime *s_rt)
+{
+ return !list_empty(&s_rt->port_list);
+}
+
+static void sdw_slave_port_free(struct sdw_slave *slave,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_port_runtime *p_rt, *_p_rt;
+ struct sdw_master_runtime *m_rt;
+ struct sdw_slave_runtime *s_rt;
+
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ if (s_rt->slave != slave)
+ continue;
+
+ list_for_each_entry_safe(p_rt, _p_rt,
+ &s_rt->port_list, port_node) {
+ sdw_port_free(p_rt);
+ }
+ }
+ }
+}
+
+static int sdw_slave_port_alloc(struct sdw_slave *slave,
+ struct sdw_slave_runtime *s_rt,
+ unsigned int num_config)
+{
+ struct sdw_port_runtime *p_rt;
+ int i;
+
+ /* Iterate for number of ports to perform initialization */
+ for (i = 0; i < num_config; i++) {
+ p_rt = sdw_port_alloc(&s_rt->port_list);
+ if (!p_rt)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int sdw_slave_port_is_valid_range(struct device *dev, int num)
+{
+ if (!SDW_VALID_PORT_RANGE(num)) {
+ dev_err(dev, "SoundWire: Invalid port number :%d\n", num);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sdw_slave_port_config(struct sdw_slave *slave,
+ struct sdw_slave_runtime *s_rt,
+ struct sdw_port_config *port_config)
+{
+ struct sdw_port_runtime *p_rt;
+ int ret;
+ int i;
+
+ i = 0;
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ /*
+ * TODO: Check valid port range as defined by DisCo/
+ * slave
+ */
+ ret = sdw_slave_port_is_valid_range(&slave->dev, port_config[i].num);
+ if (ret < 0)
+ return ret;
+
+ ret = sdw_port_config(p_rt, port_config, i);
+ if (ret < 0)
+ return ret;
+ i++;
+ }
+
+ return 0;
+}
+
+static bool sdw_master_port_allocated(struct sdw_master_runtime *m_rt)
+{
+ return !list_empty(&m_rt->port_list);
+}
+
+static void sdw_master_port_free(struct sdw_master_runtime *m_rt)
+{
+ struct sdw_port_runtime *p_rt, *_p_rt;
+
+ list_for_each_entry_safe(p_rt, _p_rt, &m_rt->port_list, port_node) {
+ sdw_port_free(p_rt);
+ }
+}
+
+static int sdw_master_port_alloc(struct sdw_master_runtime *m_rt,
+ unsigned int num_ports)
+{
+ struct sdw_port_runtime *p_rt;
+ int i;
+
+ /* Iterate for number of ports to perform initialization */
+ for (i = 0; i < num_ports; i++) {
+ p_rt = sdw_port_alloc(&m_rt->port_list);
+ if (!p_rt)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int sdw_master_port_config(struct sdw_master_runtime *m_rt,
+ struct sdw_port_config *port_config)
+{
+ struct sdw_port_runtime *p_rt;
+ int ret;
+ int i;
+
+ i = 0;
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ ret = sdw_port_config(p_rt, port_config, i);
+ if (ret < 0)
+ return ret;
+ i++;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_slave_rt_alloc() - Allocate a Slave runtime handle.
+ *
+ * @slave: Slave handle
+ * @m_rt: Master runtime handle
+ *
+ * This function is to be called with bus_lock held.
+ */
+static struct sdw_slave_runtime
+*sdw_slave_rt_alloc(struct sdw_slave *slave,
+ struct sdw_master_runtime *m_rt)
+{
+ struct sdw_slave_runtime *s_rt;
+
+ s_rt = kzalloc(sizeof(*s_rt), GFP_KERNEL);
+ if (!s_rt)
+ return NULL;
+
+ INIT_LIST_HEAD(&s_rt->port_list);
+ s_rt->slave = slave;
+
+ list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list);
+
+ return s_rt;
+}
+
+/**
+ * sdw_slave_rt_config() - Configure a Slave runtime handle.
+ *
+ * @s_rt: Slave runtime handle
+ * @stream_config: Stream configuration
+ *
+ * This function is to be called with bus_lock held.
+ */
+static int sdw_slave_rt_config(struct sdw_slave_runtime *s_rt,
+ struct sdw_stream_config *stream_config)
+{
+ s_rt->ch_count = stream_config->ch_count;
+ s_rt->direction = stream_config->direction;
+
+ return 0;
+}
+
+static struct sdw_slave_runtime *sdw_slave_rt_find(struct sdw_slave *slave,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_slave_runtime *s_rt, *_s_rt;
+ struct sdw_master_runtime *m_rt;
+
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ /* Retrieve Slave runtime handle */
+ list_for_each_entry_safe(s_rt, _s_rt,
+ &m_rt->slave_rt_list, m_rt_node) {
+ if (s_rt->slave == slave)
+ return s_rt;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * sdw_slave_rt_free() - Free Slave(s) runtime handle
+ *
+ * @slave: Slave handle.
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held.
+ */
+static void sdw_slave_rt_free(struct sdw_slave *slave,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_slave_runtime *s_rt;
+
+ s_rt = sdw_slave_rt_find(slave, stream);
+ if (s_rt) {
+ list_del(&s_rt->m_rt_node);
+ kfree(s_rt);
+ }
+}
+
+static struct sdw_master_runtime
+*sdw_master_rt_find(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+
+ /* Retrieve Bus handle if already available */
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ if (m_rt->bus == bus)
+ return m_rt;
+ }
+
+ return NULL;
+}
+
+/**
+ * sdw_master_rt_alloc() - Allocates a Master runtime handle
+ *
+ * @bus: SDW bus instance
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held.
+ */
+static struct sdw_master_runtime
+*sdw_master_rt_alloc(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+
+ m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
+ if (!m_rt)
+ return NULL;
+
+ /* Initialization of Master runtime handle */
+ INIT_LIST_HEAD(&m_rt->port_list);
+ INIT_LIST_HEAD(&m_rt->slave_rt_list);
+ list_add_tail(&m_rt->stream_node, &stream->master_list);
+
+ list_add_tail(&m_rt->bus_node, &bus->m_rt_list);
+
+ m_rt->bus = bus;
+ m_rt->stream = stream;
+
+ return m_rt;
+}
+
+/**
+ * sdw_master_rt_config() - Configure Master runtime handle
+ *
+ * @m_rt: Master runtime handle
+ * @stream_config: Stream configuration
+ *
+ * This function is to be called with bus_lock held.
+ */
+
+static int sdw_master_rt_config(struct sdw_master_runtime *m_rt,
+ struct sdw_stream_config *stream_config)
+{
+ m_rt->ch_count = stream_config->ch_count;
+ m_rt->direction = stream_config->direction;
+
+ return 0;
+}
+
+/**
+ * sdw_master_rt_free() - Free Master runtime handle
+ *
+ * @m_rt: Master runtime node
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held
+ * It frees the Master runtime handle and associated Slave(s) runtime
+ * handle. If this is called first then sdw_slave_rt_free() will have
+ * no effect as Slave(s) runtime handle would already be freed up.
+ */
+static void sdw_master_rt_free(struct sdw_master_runtime *m_rt,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_slave_runtime *s_rt, *_s_rt;
+
+ list_for_each_entry_safe(s_rt, _s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ sdw_slave_port_free(s_rt->slave, stream);
+ sdw_slave_rt_free(s_rt->slave, stream);
+ }
+
+ list_del(&m_rt->stream_node);
+ list_del(&m_rt->bus_node);
+ kfree(m_rt);
+}
+
+/**
+ * sdw_config_stream() - Configure the allocated stream
+ *
+ * @dev: SDW device
+ * @stream: SoundWire stream
+ * @stream_config: Stream configuration for audio stream
+ * @is_slave: is API called from Slave or Master
+ *
+ * This function is to be called with bus_lock held.
+ */
+static int sdw_config_stream(struct device *dev,
+ struct sdw_stream_runtime *stream,
+ struct sdw_stream_config *stream_config,
+ bool is_slave)
+{
+ /*
+ * Update the stream rate, channel and bps based on data
+ * source. For more than one data source (multilink),
+ * match the rate, bps, stream type and increment number of channels.
+ *
+ * If rate/bps is zero, it means the values are not set, so skip
+ * comparison and allow the value to be set and stored in stream
+ */
+ if (stream->params.rate &&
+ stream->params.rate != stream_config->frame_rate) {
+ dev_err(dev, "rate not matching, stream:%s\n", stream->name);
+ return -EINVAL;
+ }
+
+ if (stream->params.bps &&
+ stream->params.bps != stream_config->bps) {
+ dev_err(dev, "bps not matching, stream:%s\n", stream->name);
+ return -EINVAL;
+ }
+
+ stream->type = stream_config->type;
+ stream->params.rate = stream_config->frame_rate;
+ stream->params.bps = stream_config->bps;
+
+ /* TODO: Update this check during Device-device support */
+ if (is_slave)
+ stream->params.ch_count += stream_config->ch_count;
+
+ return 0;
+}
+
+/**
+ * sdw_get_slave_dpn_prop() - Get Slave port capabilities
+ *
+ * @slave: Slave handle
+ * @direction: Data direction.
+ * @port_num: Port number
+ */
+struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
+ enum sdw_data_direction direction,
+ unsigned int port_num)
+{
+ struct sdw_dpn_prop *dpn_prop;
+ u8 num_ports;
+ int i;
+
+ if (direction == SDW_DATA_DIR_TX) {
+ num_ports = hweight32(slave->prop.source_ports);
+ dpn_prop = slave->prop.src_dpn_prop;
+ } else {
+ num_ports = hweight32(slave->prop.sink_ports);
+ dpn_prop = slave->prop.sink_dpn_prop;
+ }
+
+ for (i = 0; i < num_ports; i++) {
+ if (dpn_prop[i].num == port_num)
+ return &dpn_prop[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * sdw_acquire_bus_lock: Acquire bus lock for all Master runtime(s)
+ *
+ * @stream: SoundWire stream
+ *
+ * Acquire bus_lock for each of the master runtime(m_rt) part of this
+ * stream to reconfigure the bus.
+ * NOTE: This function is called from SoundWire stream ops and is
+ * expected that a global lock is held before acquiring bus_lock.
+ */
+static void sdw_acquire_bus_lock(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_bus *bus;
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+
+ mutex_lock(&bus->bus_lock);
+ }
+}
+
+/**
+ * sdw_release_bus_lock: Release bus lock for all Master runtime(s)
+ *
+ * @stream: SoundWire stream
+ *
+ * Release the previously held bus_lock after reconfiguring the bus.
+ * NOTE: This function is called from SoundWire stream ops and is
+ * expected that a global lock is held before releasing bus_lock.
+ */
+static void sdw_release_bus_lock(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_bus *bus;
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry_reverse(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+ mutex_unlock(&bus->bus_lock);
+ }
+}
+
+static int _sdw_prepare_stream(struct sdw_stream_runtime *stream,
+ bool update_params)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_bus *bus = NULL;
+ struct sdw_master_prop *prop;
+ struct sdw_bus_params params;
+ int ret;
+
+ /* Prepare Master(s) and Slave(s) port(s) associated with stream */
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+ prop = &bus->prop;
+ memcpy(&params, &bus->params, sizeof(params));
+
+ /* TODO: Support Asynchronous mode */
+ if ((prop->max_clk_freq % stream->params.rate) != 0) {
+ dev_err(bus->dev, "Async mode not supported\n");
+ return -EINVAL;
+ }
+
+ if (!update_params)
+ goto program_params;
+
+ /* Increment cumulative bus bandwidth */
+ /* TODO: Update this during Device-Device support */
+ 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\n",
+ ret);
+ return ret;
+ }
+ }
+
+program_params:
+ /* Program params */
+ ret = sdw_program_params(bus, true);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program params failed: %d\n", ret);
+ goto restore_params;
+ }
+ }
+
+ if (!bus) {
+ pr_err("Configuration error in %s\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = do_bank_switch(stream);
+ if (ret < 0) {
+ dev_err(bus->dev, "Bank switch failed: %d\n", ret);
+ goto restore_params;
+ }
+
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+
+ /* Prepare port(s) on the new clock configuration */
+ ret = sdw_prep_deprep_ports(m_rt, true);
+ if (ret < 0) {
+ dev_err(bus->dev, "Prepare port(s) failed ret = %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ stream->state = SDW_STREAM_PREPARED;
+
+ return ret;
+
+restore_params:
+ memcpy(&bus->params, &params, sizeof(params));
+ return ret;
+}
+
+/**
+ * sdw_prepare_stream() - Prepare SoundWire stream
+ *
+ * @stream: Soundwire stream
+ *
+ * Documentation/driver-api/soundwire/stream.rst explains this API in detail
+ */
+int sdw_prepare_stream(struct sdw_stream_runtime *stream)
+{
+ bool update_params = true;
+ int ret;
+
+ if (!stream) {
+ pr_err("SoundWire: Handle not found for stream\n");
+ return -EINVAL;
+ }
+
+ sdw_acquire_bus_lock(stream);
+
+ if (stream->state == SDW_STREAM_PREPARED) {
+ ret = 0;
+ goto state_err;
+ }
+
+ if (stream->state != SDW_STREAM_CONFIGURED &&
+ stream->state != SDW_STREAM_DEPREPARED &&
+ stream->state != SDW_STREAM_DISABLED) {
+ pr_err("%s: %s: inconsistent state state %d\n",
+ __func__, stream->name, stream->state);
+ ret = -EINVAL;
+ goto state_err;
+ }
+
+ /*
+ * when the stream is DISABLED, this means sdw_prepare_stream()
+ * is called as a result of an underflow or a resume operation.
+ * In this case, the bus parameters shall not be recomputed, but
+ * still need to be re-applied
+ */
+ if (stream->state == SDW_STREAM_DISABLED)
+ update_params = false;
+
+ ret = _sdw_prepare_stream(stream, update_params);
+
+state_err:
+ sdw_release_bus_lock(stream);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_prepare_stream);
+
+static int _sdw_enable_stream(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_bus *bus = NULL;
+ int ret;
+
+ /* Enable Master(s) and Slave(s) port(s) associated with stream */
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+
+ /* Program params */
+ ret = sdw_program_params(bus, false);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program params failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Enable port(s) */
+ ret = sdw_enable_disable_ports(m_rt, true);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Enable port(s) failed ret: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (!bus) {
+ pr_err("Configuration error in %s\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = do_bank_switch(stream);
+ if (ret < 0) {
+ dev_err(bus->dev, "Bank switch failed: %d\n", ret);
+ return ret;
+ }
+
+ stream->state = SDW_STREAM_ENABLED;
+ return 0;
+}
+
+/**
+ * sdw_enable_stream() - Enable SoundWire stream
+ *
+ * @stream: Soundwire stream
+ *
+ * Documentation/driver-api/soundwire/stream.rst explains this API in detail
+ */
+int sdw_enable_stream(struct sdw_stream_runtime *stream)
+{
+ int ret;
+
+ if (!stream) {
+ pr_err("SoundWire: Handle not found for stream\n");
+ return -EINVAL;
+ }
+
+ sdw_acquire_bus_lock(stream);
+
+ if (stream->state == SDW_STREAM_ENABLED) {
+ ret = 0;
+ goto state_err;
+ }
+
+ if (stream->state != SDW_STREAM_PREPARED &&
+ stream->state != SDW_STREAM_DISABLED) {
+ pr_err("%s: %s: inconsistent state state %d\n",
+ __func__, stream->name, stream->state);
+ ret = -EINVAL;
+ goto state_err;
+ }
+
+ ret = _sdw_enable_stream(stream);
+
+state_err:
+ sdw_release_bus_lock(stream);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_enable_stream);
+
+static int _sdw_disable_stream(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+ int ret;
+
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ struct sdw_bus *bus = m_rt->bus;
+
+ /* Disable port(s) */
+ ret = sdw_enable_disable_ports(m_rt, false);
+ if (ret < 0) {
+ dev_err(bus->dev, "Disable port(s) failed: %d\n", ret);
+ return ret;
+ }
+ }
+ stream->state = SDW_STREAM_DISABLED;
+
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ struct sdw_bus *bus = m_rt->bus;
+
+ /* Program params */
+ ret = sdw_program_params(bus, false);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program params failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ ret = do_bank_switch(stream);
+ if (ret < 0) {
+ pr_err("Bank switch failed: %d\n", ret);
+ return ret;
+ }
+
+ /* make sure alternate bank (previous current) is also disabled */
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ struct sdw_bus *bus = m_rt->bus;
+
+ /* Disable port(s) */
+ ret = sdw_enable_disable_ports(m_rt, false);
+ if (ret < 0) {
+ dev_err(bus->dev, "Disable port(s) failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_disable_stream() - Disable SoundWire stream
+ *
+ * @stream: Soundwire stream
+ *
+ * Documentation/driver-api/soundwire/stream.rst explains this API in detail
+ */
+int sdw_disable_stream(struct sdw_stream_runtime *stream)
+{
+ int ret;
+
+ if (!stream) {
+ pr_err("SoundWire: Handle not found for stream\n");
+ return -EINVAL;
+ }
+
+ sdw_acquire_bus_lock(stream);
+
+ if (stream->state == SDW_STREAM_DISABLED) {
+ ret = 0;
+ goto state_err;
+ }
+
+ if (stream->state != SDW_STREAM_ENABLED) {
+ pr_err("%s: %s: inconsistent state state %d\n",
+ __func__, stream->name, stream->state);
+ ret = -EINVAL;
+ goto state_err;
+ }
+
+ ret = _sdw_disable_stream(stream);
+
+state_err:
+ sdw_release_bus_lock(stream);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_disable_stream);
+
+static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_bus *bus;
+ int ret = 0;
+
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ bus = m_rt->bus;
+ /* De-prepare port(s) */
+ ret = sdw_prep_deprep_ports(m_rt, false);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "De-prepare port(s) failed: %d\n", ret);
+ return ret;
+ }
+
+ /* TODO: Update this during Device-Device support */
+ 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\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /* Program params */
+ ret = sdw_program_params(bus, false);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program params failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ stream->state = SDW_STREAM_DEPREPARED;
+ return do_bank_switch(stream);
+}
+
+/**
+ * sdw_deprepare_stream() - Deprepare SoundWire stream
+ *
+ * @stream: Soundwire stream
+ *
+ * Documentation/driver-api/soundwire/stream.rst explains this API in detail
+ */
+int sdw_deprepare_stream(struct sdw_stream_runtime *stream)
+{
+ int ret;
+
+ if (!stream) {
+ pr_err("SoundWire: Handle not found for stream\n");
+ return -EINVAL;
+ }
+
+ sdw_acquire_bus_lock(stream);
+
+ if (stream->state == SDW_STREAM_DEPREPARED) {
+ ret = 0;
+ goto state_err;
+ }
+
+ if (stream->state != SDW_STREAM_PREPARED &&
+ stream->state != SDW_STREAM_DISABLED) {
+ pr_err("%s: %s: inconsistent state state %d\n",
+ __func__, stream->name, stream->state);
+ ret = -EINVAL;
+ goto state_err;
+ }
+
+ ret = _sdw_deprepare_stream(stream);
+
+state_err:
+ sdw_release_bus_lock(stream);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_deprepare_stream);
+
+static int set_stream(struct snd_pcm_substream *substream,
+ struct sdw_stream_runtime *sdw_stream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *dai;
+ int ret = 0;
+ int i;
+
+ /* Set stream pointer on all DAIs */
+ for_each_rtd_dais(rtd, i, dai) {
+ ret = snd_soc_dai_set_stream(dai, sdw_stream, substream->stream);
+ if (ret < 0) {
+ dev_err(rtd->dev, "failed to set stream pointer on dai %s\n", dai->name);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_alloc_stream() - Allocate and return stream runtime
+ *
+ * @stream_name: SoundWire stream name
+ *
+ * Allocates a SoundWire stream runtime instance.
+ * sdw_alloc_stream should be called only once per stream. Typically
+ * invoked from ALSA/ASoC machine/platform driver.
+ */
+struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name)
+{
+ struct sdw_stream_runtime *stream;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return NULL;
+
+ stream->name = stream_name;
+ INIT_LIST_HEAD(&stream->master_list);
+ stream->state = SDW_STREAM_ALLOCATED;
+ stream->m_rt_count = 0;
+
+ return stream;
+}
+EXPORT_SYMBOL(sdw_alloc_stream);
+
+/**
+ * sdw_startup_stream() - Startup SoundWire stream
+ *
+ * @sdw_substream: Soundwire stream
+ *
+ * Documentation/driver-api/soundwire/stream.rst explains this API in detail
+ */
+int sdw_startup_stream(void *sdw_substream)
+{
+ struct snd_pcm_substream *substream = sdw_substream;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sdw_stream_runtime *sdw_stream;
+ char *name;
+ int ret;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ name = kasprintf(GFP_KERNEL, "%s-Playback", substream->name);
+ else
+ name = kasprintf(GFP_KERNEL, "%s-Capture", substream->name);
+
+ if (!name)
+ return -ENOMEM;
+
+ sdw_stream = sdw_alloc_stream(name);
+ if (!sdw_stream) {
+ dev_err(rtd->dev, "alloc stream failed for substream DAI %s\n", substream->name);
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ret = set_stream(substream, sdw_stream);
+ if (ret < 0)
+ goto release_stream;
+ return 0;
+
+release_stream:
+ sdw_release_stream(sdw_stream);
+ set_stream(substream, NULL);
+error:
+ kfree(name);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_startup_stream);
+
+/**
+ * sdw_shutdown_stream() - Shutdown SoundWire stream
+ *
+ * @sdw_substream: Soundwire stream
+ *
+ * Documentation/driver-api/soundwire/stream.rst explains this API in detail
+ */
+void sdw_shutdown_stream(void *sdw_substream)
+{
+ struct snd_pcm_substream *substream = sdw_substream;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sdw_stream_runtime *sdw_stream;
+ struct snd_soc_dai *dai;
+
+ /* Find stream from first CPU DAI */
+ dai = asoc_rtd_to_cpu(rtd, 0);
+
+ sdw_stream = snd_soc_dai_get_stream(dai, substream->stream);
+
+ if (IS_ERR(sdw_stream)) {
+ dev_err(rtd->dev, "no stream found for DAI %s\n", dai->name);
+ return;
+ }
+
+ /* release memory */
+ kfree(sdw_stream->name);
+ sdw_release_stream(sdw_stream);
+
+ /* clear DAI data */
+ set_stream(substream, NULL);
+}
+EXPORT_SYMBOL(sdw_shutdown_stream);
+
+/**
+ * sdw_release_stream() - Free the assigned stream runtime
+ *
+ * @stream: SoundWire stream runtime
+ *
+ * sdw_release_stream should be called only once per stream
+ */
+void sdw_release_stream(struct sdw_stream_runtime *stream)
+{
+ kfree(stream);
+}
+EXPORT_SYMBOL(sdw_release_stream);
+
+/**
+ * sdw_stream_add_master() - Allocate and add master runtime to a stream
+ *
+ * @bus: SDW Bus instance
+ * @stream_config: Stream configuration for audio stream
+ * @port_config: Port configuration for audio stream
+ * @num_ports: Number of ports
+ * @stream: SoundWire stream
+ */
+int sdw_stream_add_master(struct sdw_bus *bus,
+ struct sdw_stream_config *stream_config,
+ struct sdw_port_config *port_config,
+ unsigned int num_ports,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+ bool alloc_master_rt = true;
+ int ret;
+
+ mutex_lock(&bus->bus_lock);
+
+ /*
+ * For multi link streams, add the second master only if
+ * the bus supports it.
+ * Check if bus->multi_link is set
+ */
+ if (!bus->multi_link && stream->m_rt_count > 0) {
+ dev_err(bus->dev,
+ "Multilink not supported, link %d\n", bus->link_id);
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ /*
+ * check if Master is already allocated (e.g. as a result of Slave adding
+ * it first), if so skip allocation and go to configuration
+ */
+ m_rt = sdw_master_rt_find(bus, stream);
+ if (m_rt) {
+ alloc_master_rt = false;
+ goto skip_alloc_master_rt;
+ }
+
+ m_rt = sdw_master_rt_alloc(bus, stream);
+ if (!m_rt) {
+ dev_err(bus->dev, "Master runtime alloc failed for stream:%s\n", stream->name);
+ ret = -ENOMEM;
+ goto unlock;
+ }
+skip_alloc_master_rt:
+
+ if (sdw_master_port_allocated(m_rt))
+ goto skip_alloc_master_port;
+
+ ret = sdw_master_port_alloc(m_rt, num_ports);
+ if (ret)
+ goto alloc_error;
+
+ stream->m_rt_count++;
+
+skip_alloc_master_port:
+
+ ret = sdw_master_rt_config(m_rt, stream_config);
+ if (ret < 0)
+ goto unlock;
+
+ ret = sdw_config_stream(bus->dev, stream, stream_config, false);
+ if (ret)
+ goto unlock;
+
+ ret = sdw_master_port_config(m_rt, port_config);
+
+ goto unlock;
+
+alloc_error:
+ /*
+ * we only cleanup what was allocated in this routine
+ */
+ if (alloc_master_rt)
+ sdw_master_rt_free(m_rt, stream);
+unlock:
+ mutex_unlock(&bus->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_stream_add_master);
+
+/**
+ * sdw_stream_remove_master() - Remove master from sdw_stream
+ *
+ * @bus: SDW Bus instance
+ * @stream: SoundWire stream
+ *
+ * This removes and frees port_rt and master_rt from a stream
+ */
+int sdw_stream_remove_master(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt, *_m_rt;
+
+ mutex_lock(&bus->bus_lock);
+
+ list_for_each_entry_safe(m_rt, _m_rt,
+ &stream->master_list, stream_node) {
+ if (m_rt->bus != bus)
+ continue;
+
+ sdw_master_port_free(m_rt);
+ sdw_master_rt_free(m_rt, stream);
+ stream->m_rt_count--;
+ }
+
+ if (list_empty(&stream->master_list))
+ stream->state = SDW_STREAM_RELEASED;
+
+ mutex_unlock(&bus->bus_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_stream_remove_master);
+
+/**
+ * sdw_stream_add_slave() - Allocate and add master/slave runtime to a stream
+ *
+ * @slave: SDW Slave instance
+ * @stream_config: Stream configuration for audio stream
+ * @stream: SoundWire stream
+ * @port_config: Port configuration for audio stream
+ * @num_ports: Number of ports
+ *
+ * It is expected that Slave is added before adding Master
+ * to the Stream.
+ *
+ */
+int sdw_stream_add_slave(struct sdw_slave *slave,
+ struct sdw_stream_config *stream_config,
+ struct sdw_port_config *port_config,
+ unsigned int num_ports,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_master_runtime *m_rt;
+ bool alloc_master_rt = true;
+ bool alloc_slave_rt = true;
+
+ int ret;
+
+ mutex_lock(&slave->bus->bus_lock);
+
+ /*
+ * check if Master is already allocated, if so skip allocation
+ * and go to configuration
+ */
+ m_rt = sdw_master_rt_find(slave->bus, stream);
+ if (m_rt) {
+ alloc_master_rt = false;
+ goto skip_alloc_master_rt;
+ }
+
+ /*
+ * If this API is invoked by Slave first then m_rt is not valid.
+ * So, allocate m_rt and add Slave to it.
+ */
+ m_rt = sdw_master_rt_alloc(slave->bus, stream);
+ if (!m_rt) {
+ dev_err(&slave->dev, "Master runtime alloc failed for stream:%s\n", stream->name);
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+skip_alloc_master_rt:
+ s_rt = sdw_slave_rt_find(slave, stream);
+ if (s_rt) {
+ alloc_slave_rt = false;
+ goto skip_alloc_slave_rt;
+ }
+
+ s_rt = sdw_slave_rt_alloc(slave, m_rt);
+ if (!s_rt) {
+ dev_err(&slave->dev, "Slave runtime alloc failed for stream:%s\n", stream->name);
+ alloc_slave_rt = false;
+ ret = -ENOMEM;
+ goto alloc_error;
+ }
+
+skip_alloc_slave_rt:
+ if (sdw_slave_port_allocated(s_rt))
+ goto skip_port_alloc;
+
+ ret = sdw_slave_port_alloc(slave, s_rt, num_ports);
+ if (ret)
+ goto alloc_error;
+
+skip_port_alloc:
+ ret = sdw_master_rt_config(m_rt, stream_config);
+ if (ret)
+ goto unlock;
+
+ ret = sdw_slave_rt_config(s_rt, stream_config);
+ if (ret)
+ goto unlock;
+
+ ret = sdw_config_stream(&slave->dev, stream, stream_config, true);
+ if (ret)
+ goto unlock;
+
+ ret = sdw_slave_port_config(slave, s_rt, port_config);
+ if (ret)
+ goto unlock;
+
+ /*
+ * Change stream state to CONFIGURED on first Slave add.
+ * Bus is not aware of number of Slave(s) in a stream at this
+ * point so cannot depend on all Slave(s) to be added in order to
+ * change stream state to CONFIGURED.
+ */
+ stream->state = SDW_STREAM_CONFIGURED;
+ goto unlock;
+
+alloc_error:
+ /*
+ * we only cleanup what was allocated in this routine. The 'else if'
+ * is intentional, the 'master_rt_free' will call sdw_slave_rt_free()
+ * internally.
+ */
+ if (alloc_master_rt)
+ sdw_master_rt_free(m_rt, stream);
+ else if (alloc_slave_rt)
+ sdw_slave_rt_free(slave, stream);
+unlock:
+ mutex_unlock(&slave->bus->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_stream_add_slave);
+
+/**
+ * sdw_stream_remove_slave() - Remove slave from sdw_stream
+ *
+ * @slave: SDW Slave instance
+ * @stream: SoundWire stream
+ *
+ * This removes and frees port_rt and slave_rt from a stream
+ */
+int sdw_stream_remove_slave(struct sdw_slave *slave,
+ struct sdw_stream_runtime *stream)
+{
+ mutex_lock(&slave->bus->bus_lock);
+
+ sdw_slave_port_free(slave, stream);
+ sdw_slave_rt_free(slave, stream);
+
+ mutex_unlock(&slave->bus->bus_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_stream_remove_slave);
diff --git a/drivers/soundwire/sysfs_local.h b/drivers/soundwire/sysfs_local.h
new file mode 100644
index 000000000..7268bc24c
--- /dev/null
+++ b/drivers/soundwire/sysfs_local.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright(c) 2015-2020 Intel Corporation. */
+
+#ifndef __SDW_SYSFS_LOCAL_H
+#define __SDW_SYSFS_LOCAL_H
+
+/*
+ * 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);
+
+#endif /* __SDW_SYSFS_LOCAL_H */
diff --git a/drivers/soundwire/sysfs_slave.c b/drivers/soundwire/sysfs_slave.c
new file mode 100644
index 000000000..3210359cd
--- /dev/null
+++ b/drivers/soundwire/sysfs_slave.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2015-2020 Intel Corporation.
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+#include "sysfs_local.h"
+
+/*
+ * Slave sysfs
+ */
+
+/*
+ * The sysfs for Slave reflects the MIPI description as given
+ * 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
+ * |---- wake_capable
+ * |---- test_mode_capable
+ * |---- clk_stop_mode1
+ * |---- simple_clk_stop_capable
+ * |---- clk_stop_timeout
+ * |---- ch_prep_timeout
+ * |---- reset_behave
+ * |---- high_PHY_capable
+ * |---- paging_support
+ * |---- bank_delay_support
+ * |---- p15_behave
+ * |---- master_count
+ * |---- source_ports
+ * |---- sink_ports
+ * |---- dp0
+ * |---- max_word
+ * |---- min_word
+ * |---- words
+ * |---- BRA_flow_controlled
+ * |---- simple_ch_prep_sm
+ * |---- imp_def_interrupts
+ * |---- dpN_<sink/src>
+ * |---- max_word
+ * |---- min_word
+ * |---- words
+ * |---- type
+ * |---- max_grouping
+ * |---- simple_ch_prep_sm
+ * |---- ch_prep_timeout
+ * |---- imp_def_interrupts
+ * |---- min_ch
+ * |---- max_ch
+ * |---- channels
+ * |---- ch_combinations
+ * |---- max_async_buffer
+ * |---- block_pack_mode
+ * |---- port_encoding
+ *
+ */
+
+#define sdw_slave_attr(field, format_string) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_slave *slave = dev_to_sdw_dev(dev); \
+ return sprintf(buf, format_string, slave->prop.field); \
+} \
+static DEVICE_ATTR_RO(field)
+
+sdw_slave_attr(mipi_revision, "0x%x\n");
+sdw_slave_attr(wake_capable, "%d\n");
+sdw_slave_attr(test_mode_capable, "%d\n");
+sdw_slave_attr(clk_stop_mode1, "%d\n");
+sdw_slave_attr(simple_clk_stop_capable, "%d\n");
+sdw_slave_attr(clk_stop_timeout, "%d\n");
+sdw_slave_attr(ch_prep_timeout, "%d\n");
+sdw_slave_attr(reset_behave, "%d\n");
+sdw_slave_attr(high_PHY_capable, "%d\n");
+sdw_slave_attr(paging_support, "%d\n");
+sdw_slave_attr(bank_delay_support, "%d\n");
+sdw_slave_attr(p15_behave, "%d\n");
+sdw_slave_attr(master_count, "%d\n");
+sdw_slave_attr(source_ports, "0x%x\n");
+sdw_slave_attr(sink_ports, "0x%x\n");
+
+static ssize_t modalias_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+
+ return sdw_slave_modalias(slave, buf, 256);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *slave_attrs[] = {
+ &dev_attr_modalias.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(slave);
+
+static struct attribute *slave_dev_attrs[] = {
+ &dev_attr_mipi_revision.attr,
+ &dev_attr_wake_capable.attr,
+ &dev_attr_test_mode_capable.attr,
+ &dev_attr_clk_stop_mode1.attr,
+ &dev_attr_simple_clk_stop_capable.attr,
+ &dev_attr_clk_stop_timeout.attr,
+ &dev_attr_ch_prep_timeout.attr,
+ &dev_attr_reset_behave.attr,
+ &dev_attr_high_PHY_capable.attr,
+ &dev_attr_paging_support.attr,
+ &dev_attr_bank_delay_support.attr,
+ &dev_attr_p15_behave.attr,
+ &dev_attr_master_count.attr,
+ &dev_attr_source_ports.attr,
+ &dev_attr_sink_ports.attr,
+ NULL,
+};
+
+/*
+ * we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory
+ * for device-level properties
+ */
+static const struct attribute_group sdw_slave_dev_attr_group = {
+ .attrs = slave_dev_attrs,
+ .name = "dev-properties",
+};
+
+/*
+ * DP0 sysfs
+ */
+
+#define sdw_dp0_attr(field, format_string) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_slave *slave = dev_to_sdw_dev(dev); \
+ return sprintf(buf, format_string, slave->prop.dp0_prop->field);\
+} \
+static DEVICE_ATTR_RO(field)
+
+sdw_dp0_attr(max_word, "%d\n");
+sdw_dp0_attr(min_word, "%d\n");
+sdw_dp0_attr(BRA_flow_controlled, "%d\n");
+sdw_dp0_attr(simple_ch_prep_sm, "%d\n");
+sdw_dp0_attr(imp_def_interrupts, "0x%x\n");
+
+static ssize_t words_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ ssize_t size = 0;
+ int i;
+
+ for (i = 0; i < slave->prop.dp0_prop->num_words; i++)
+ size += sprintf(buf + size, "%d ",
+ slave->prop.dp0_prop->words[i]);
+ size += sprintf(buf + size, "\n");
+
+ return size;
+}
+static DEVICE_ATTR_RO(words);
+
+static struct attribute *dp0_attrs[] = {
+ &dev_attr_max_word.attr,
+ &dev_attr_min_word.attr,
+ &dev_attr_words.attr,
+ &dev_attr_BRA_flow_controlled.attr,
+ &dev_attr_simple_ch_prep_sm.attr,
+ &dev_attr_imp_def_interrupts.attr,
+ NULL,
+};
+
+/*
+ * we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory
+ * for dp0-level properties
+ */
+static const struct attribute_group dp0_group = {
+ .attrs = dp0_attrs,
+ .name = "dp0",
+};
+
+int sdw_slave_sysfs_init(struct sdw_slave *slave)
+{
+ int ret;
+
+ ret = devm_device_add_groups(&slave->dev, slave_groups);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_device_add_group(&slave->dev, &sdw_slave_dev_attr_group);
+ if (ret < 0)
+ return ret;
+
+ if (slave->prop.dp0_prop) {
+ ret = devm_device_add_group(&slave->dev, &dp0_group);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (slave->prop.source_ports || slave->prop.sink_ports) {
+ ret = sdw_slave_sysfs_dpn_init(slave);
+ if (ret < 0)
+ return ret;
+ }
+
+ 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
+};
diff --git a/drivers/soundwire/sysfs_slave_dpn.c b/drivers/soundwire/sysfs_slave_dpn.c
new file mode 100644
index 000000000..c4b6543c0
--- /dev/null
+++ b/drivers/soundwire/sysfs_slave_dpn.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2015-2020 Intel Corporation.
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+#include "sysfs_local.h"
+
+struct dpn_attribute {
+ struct device_attribute dev_attr;
+ int N;
+ int dir;
+ const char *format_string;
+};
+
+/*
+ * Since we can't use ARRAY_SIZE, hard-code number of dpN attributes.
+ * This needs to be updated when adding new attributes - an error will be
+ * flagged on a mismatch.
+ */
+#define SDW_DPN_ATTRIBUTES 15
+
+#define sdw_dpn_attribute_alloc(field) \
+static int field##_attribute_alloc(struct device *dev, \
+ struct attribute **res, \
+ int N, int dir, \
+ const char *format_string) \
+{ \
+ struct dpn_attribute *dpn_attr; \
+ \
+ dpn_attr = devm_kzalloc(dev, sizeof(*dpn_attr), GFP_KERNEL); \
+ if (!dpn_attr) \
+ return -ENOMEM; \
+ dpn_attr->N = N; \
+ dpn_attr->dir = dir; \
+ sysfs_attr_init(&dpn_attr->dev_attr.attr); \
+ dpn_attr->format_string = format_string; \
+ dpn_attr->dev_attr.attr.name = __stringify(field); \
+ dpn_attr->dev_attr.attr.mode = 0444; \
+ dpn_attr->dev_attr.show = field##_show; \
+ \
+ *res = &dpn_attr->dev_attr.attr; \
+ \
+ return 0; \
+}
+
+#define sdw_dpn_attr(field) \
+ \
+static ssize_t field##_dpn_show(struct sdw_slave *slave, \
+ int N, \
+ int dir, \
+ const char *format_string, \
+ char *buf) \
+{ \
+ struct sdw_dpn_prop *dpn; \
+ unsigned long mask; \
+ int bit; \
+ int i; \
+ \
+ if (dir) { \
+ dpn = slave->prop.src_dpn_prop; \
+ mask = slave->prop.source_ports; \
+ } else { \
+ dpn = slave->prop.sink_dpn_prop; \
+ mask = slave->prop.sink_ports; \
+ } \
+ \
+ i = 0; \
+ for_each_set_bit(bit, &mask, 32) { \
+ if (bit == N) { \
+ return sprintf(buf, format_string, \
+ dpn[i].field); \
+ } \
+ i++; \
+ } \
+ return -EINVAL; \
+} \
+ \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_slave *slave = dev_to_sdw_dev(dev); \
+ struct dpn_attribute *dpn_attr = \
+ container_of(attr, struct dpn_attribute, dev_attr); \
+ \
+ return field##_dpn_show(slave, \
+ dpn_attr->N, dpn_attr->dir, \
+ dpn_attr->format_string, \
+ buf); \
+} \
+sdw_dpn_attribute_alloc(field)
+
+sdw_dpn_attr(imp_def_interrupts);
+sdw_dpn_attr(max_word);
+sdw_dpn_attr(min_word);
+sdw_dpn_attr(type);
+sdw_dpn_attr(max_grouping);
+sdw_dpn_attr(simple_ch_prep_sm);
+sdw_dpn_attr(ch_prep_timeout);
+sdw_dpn_attr(max_ch);
+sdw_dpn_attr(min_ch);
+sdw_dpn_attr(max_async_buffer);
+sdw_dpn_attr(block_pack_mode);
+sdw_dpn_attr(port_encoding);
+
+#define sdw_dpn_array_attr(field) \
+ \
+static ssize_t field##_dpn_show(struct sdw_slave *slave, \
+ int N, \
+ int dir, \
+ const char *format_string, \
+ char *buf) \
+{ \
+ struct sdw_dpn_prop *dpn; \
+ unsigned long mask; \
+ ssize_t size = 0; \
+ int bit; \
+ int i; \
+ int j; \
+ \
+ if (dir) { \
+ dpn = slave->prop.src_dpn_prop; \
+ mask = slave->prop.source_ports; \
+ } else { \
+ dpn = slave->prop.sink_dpn_prop; \
+ mask = slave->prop.sink_ports; \
+ } \
+ \
+ i = 0; \
+ for_each_set_bit(bit, &mask, 32) { \
+ if (bit == N) { \
+ for (j = 0; j < dpn[i].num_##field; j++) \
+ size += sprintf(buf + size, \
+ format_string, \
+ dpn[i].field[j]); \
+ size += sprintf(buf + size, "\n"); \
+ return size; \
+ } \
+ i++; \
+ } \
+ return -EINVAL; \
+} \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_slave *slave = dev_to_sdw_dev(dev); \
+ struct dpn_attribute *dpn_attr = \
+ container_of(attr, struct dpn_attribute, dev_attr); \
+ \
+ return field##_dpn_show(slave, \
+ dpn_attr->N, dpn_attr->dir, \
+ dpn_attr->format_string, \
+ buf); \
+} \
+sdw_dpn_attribute_alloc(field)
+
+sdw_dpn_array_attr(words);
+sdw_dpn_array_attr(ch_combinations);
+sdw_dpn_array_attr(channels);
+
+static int add_all_attributes(struct device *dev, int N, int dir)
+{
+ struct attribute **dpn_attrs;
+ struct attribute_group *dpn_group;
+ int i = 0;
+ int ret;
+
+ /* allocate attributes, last one is NULL */
+ dpn_attrs = devm_kcalloc(dev, SDW_DPN_ATTRIBUTES + 1,
+ sizeof(struct attribute *),
+ GFP_KERNEL);
+ if (!dpn_attrs)
+ return -ENOMEM;
+
+ ret = max_word_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = min_word_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = words_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = type_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = max_grouping_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = simple_ch_prep_sm_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = ch_prep_timeout_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = imp_def_interrupts_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "0x%x\n");
+ if (ret < 0)
+ return ret;
+
+ ret = min_ch_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = max_ch_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = channels_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = ch_combinations_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = max_async_buffer_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = block_pack_mode_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = port_encoding_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ /* paranoia check for editing mistakes */
+ if (i != SDW_DPN_ATTRIBUTES) {
+ dev_err(dev, "mismatch in attributes, allocated %d got %d\n",
+ SDW_DPN_ATTRIBUTES, i);
+ return -EINVAL;
+ }
+
+ dpn_group = devm_kzalloc(dev, sizeof(*dpn_group), GFP_KERNEL);
+ if (!dpn_group)
+ return -ENOMEM;
+
+ dpn_group->attrs = dpn_attrs;
+ dpn_group->name = devm_kasprintf(dev, GFP_KERNEL, "dp%d_%s",
+ N, dir ? "src" : "sink");
+ if (!dpn_group->name)
+ return -ENOMEM;
+
+ ret = devm_device_add_group(dev, dpn_group);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave)
+{
+ unsigned long mask;
+ int ret;
+ int i;
+
+ mask = slave->prop.source_ports;
+ for_each_set_bit(i, &mask, 32) {
+ ret = add_all_attributes(&slave->dev, i, 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ mask = slave->prop.sink_ports;
+ for_each_set_bit(i, &mask, 32) {
+ ret = add_all_attributes(&slave->dev, i, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}