summaryrefslogtreecommitdiffstats
path: root/drivers/soundwire
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soundwire')
-rw-r--r--drivers/soundwire/Kconfig37
-rw-r--r--drivers/soundwire/Makefile18
-rw-r--r--drivers/soundwire/bus.c997
-rw-r--r--drivers/soundwire/bus.h71
-rw-r--r--drivers/soundwire/bus_type.c193
-rw-r--r--drivers/soundwire/cadence_master.c751
-rw-r--r--drivers/soundwire/cadence_master.h48
-rw-r--r--drivers/soundwire/intel.c345
-rw-r--r--drivers/soundwire/intel.h23
-rw-r--r--drivers/soundwire/intel_init.c198
-rw-r--r--drivers/soundwire/mipi_disco.c401
-rw-r--r--drivers/soundwire/slave.c114
12 files changed, 3196 insertions, 0 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig
new file mode 100644
index 000000000000..b46084b4b1f8
--- /dev/null
+++ b/drivers/soundwire/Kconfig
@@ -0,0 +1,37 @@
+#
+# SoundWire subsystem configuration
+#
+
+menuconfig SOUNDWIRE
+ bool "SoundWire support"
+ ---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_BUS
+ tristate
+ select REGMAP_SOUNDWIRE
+
+config SOUNDWIRE_CADENCE
+ tristate
+
+config SOUNDWIRE_INTEL
+ tristate "Intel SoundWire Master driver"
+ select SOUNDWIRE_CADENCE
+ select SOUNDWIRE_BUS
+ depends on X86 && ACPI
+ ---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.
+
+endif
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile
new file mode 100644
index 000000000000..e1a74c5692aa
--- /dev/null
+++ b/drivers/soundwire/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for soundwire core
+#
+
+#Bus Objs
+soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o
+obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o
+
+#Cadence Objs
+soundwire-cadence-objs := cadence_master.o
+obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o
+
+#Intel driver
+soundwire-intel-objs := intel.o
+obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o
+
+soundwire-intel-init-objs := intel_init.o
+obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel-init.o
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c
new file mode 100644
index 000000000000..d6dc8e7a8614
--- /dev/null
+++ b/drivers/soundwire/bus.c
@@ -0,0 +1,997 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+#include <linux/acpi.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include "bus.h"
+
+/**
+ * sdw_add_bus_master() - add a bus Master instance
+ * @bus: bus instance
+ *
+ * Initializes the bus instance, read properties and create child
+ * devices.
+ */
+int sdw_add_bus_master(struct sdw_bus *bus)
+{
+ int ret;
+
+ if (!bus->dev) {
+ pr_err("SoundWire bus has no device");
+ return -ENODEV;
+ }
+
+ if (!bus->ops) {
+ dev_err(bus->dev, "SoundWire Bus ops are not set");
+ return -EINVAL;
+ }
+
+ mutex_init(&bus->msg_lock);
+ mutex_init(&bus->bus_lock);
+ INIT_LIST_HEAD(&bus->slaves);
+
+ if (bus->ops->read_prop) {
+ ret = bus->ops->read_prop(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Bus read properties failed:%d", ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Device numbers in SoundWire are 0 thru 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
+ ret = -ENOTSUPP; /* No ACPI/DT so error out */
+
+ if (ret) {
+ dev_err(bus->dev, "Finding slaves failed:%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_add_bus_master);
+
+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;
+
+ mutex_lock(&bus->bus_lock);
+
+ if (slave->dev_num) /* clear dev_num if assigned */
+ clear_bit(slave->dev_num, bus->assigned);
+
+ list_del_init(&slave->node);
+ mutex_unlock(&bus->bus_lock);
+
+ device_unregister(dev);
+ return 0;
+}
+
+/**
+ * sdw_delete_bus_master() - delete the bus master instance
+ * @bus: bus to be deleted
+ *
+ * Remove the instance, delete the child devices.
+ */
+void sdw_delete_bus_master(struct sdw_bus *bus)
+{
+ device_for_each_child(bus->dev, NULL, sdw_delete_slave);
+}
+EXPORT_SYMBOL(sdw_delete_bus_master);
+
+/*
+ * 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;
+
+ 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;
+}
+
+/**
+ * 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 = do_transfer(bus, msg);
+ if (ret != 0 && ret != -ENODATA)
+ dev_err(bus->dev, "trf on Slave %d failed:%d\n",
+ msg->dev_num, ret);
+
+ if (msg->page)
+ sdw_reset_page(bus, msg->dev_num);
+
+ mutex_unlock(&bus->msg_lock);
+
+ return ret;
+}
+
+/**
+ * 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;
+ msg->ssp_sync = false;
+ msg->page = false;
+
+ if (addr < SDW_REG_NO_PAGE) { /* no paging area */
+ return 0;
+ } else 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 thru 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;
+ } else if (!slave->prop.paging_support) {
+ dev_err(&slave->dev,
+ "address %x needs paging but no support", addr);
+ return -EINVAL;
+ }
+
+ msg->addr_page1 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE1_MASK));
+ msg->addr_page2 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE2_MASK));
+ msg->addr |= BIT(15);
+ msg->page = true;
+
+ return 0;
+}
+
+/**
+ * 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)
+{
+ 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 = pm_runtime_get_sync(slave->bus->dev);
+ if (ret < 0)
+ return ret;
+
+ ret = sdw_transfer(slave->bus, &msg);
+ pm_runtime_put(slave->bus->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 read
+ */
+int sdw_nwrite(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_WRITE, val);
+ if (ret < 0)
+ return ret;
+
+ ret = pm_runtime_get_sync(slave->bus->dev);
+ if (ret < 0)
+ return ret;
+
+ ret = sdw_transfer(slave->bus, &msg);
+ pm_runtime_put(slave->bus->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;
+ else
+ 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 = NULL;
+
+ list_for_each_entry(slave, &bus->slaves, node) {
+ if (slave->dev_num == i)
+ return slave;
+ }
+
+ return NULL;
+}
+
+static int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id)
+{
+
+ if ((slave->id.unique_id != id.unique_id) ||
+ (slave->id.mfg_id != id.mfg_id) ||
+ (slave->id.part_id != id.part_id) ||
+ (slave->id.class_id != id.class_id))
+ return -ENODEV;
+
+ return 0;
+}
+
+/* called with bus_lock held */
+static int sdw_get_device_num(struct sdw_slave *slave)
+{
+ int bit;
+
+ 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)
+{
+ int ret, dev_num;
+
+ /* check first if device number is assigned, if so reuse that */
+ if (!slave->dev_num) {
+ 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(slave->bus->dev, "Get dev_num failed: %d",
+ dev_num);
+ return dev_num;
+ }
+ } else {
+ dev_info(slave->bus->dev,
+ "Slave already registered dev_num:%d",
+ 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(slave, SDW_SCP_DEVNUMBER, dev_num);
+ if (ret < 0) {
+ dev_err(&slave->dev, "Program device_num failed: %d", ret);
+ return ret;
+ }
+
+ /* After xfer of msg, restore dev_num */
+ slave->dev_num = dev_num;
+
+ 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", addr);
+
+ /*
+ * Spec definition
+ * Register Bit Contents
+ * DevId_0 [7:4] 47:44 sdw_version
+ * DevId_0 [3:0] 43:40 unique_id
+ * DevId_1 39:32 mfg_id [15:8]
+ * DevId_2 31:24 mfg_id [7:0]
+ * DevId_3 23:16 part_id [15:8]
+ * DevId_4 15:08 part_id [7:0]
+ * DevId_5 07:00 class_id
+ */
+ id->sdw_version = (addr >> 44) & GENMASK(3, 0);
+ id->unique_id = (addr >> 40) & GENMASK(3, 0);
+ id->mfg_id = (addr >> 24) & GENMASK(15, 0);
+ id->part_id = (addr >> 8) & GENMASK(15, 0);
+ id->class_id = addr & GENMASK(7, 0);
+
+ dev_dbg(bus->dev,
+ "SDW Slave class_id %x, part_id %x, mfg_id %x, unique_id %x, version %x",
+ id->class_id, id->part_id, id->mfg_id,
+ id->unique_id, id->sdw_version);
+
+}
+
+static int sdw_program_device_num(struct sdw_bus *bus)
+{
+ u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0};
+ struct sdw_slave *slave, *_s;
+ struct sdw_slave_id id;
+ struct sdw_msg msg;
+ bool found = false;
+ int count = 0, ret;
+ u64 addr;
+
+ /* 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 */
+ 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);
+
+ /* Now compare with entries */
+ list_for_each_entry_safe(slave, _s, &bus->slaves, node) {
+ if (sdw_compare_devid(slave, id) == 0) {
+ found = true;
+
+ /*
+ * 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) {
+ dev_err(slave->bus->dev,
+ "Assign dev_num failed:%d",
+ ret);
+ return ret;
+ }
+
+ break;
+ }
+ }
+
+ if (found == false) {
+ /* TODO: Park this device in Group 13 */
+ dev_err(bus->dev, "Slave Entry not found");
+ }
+
+ 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)
+{
+ mutex_lock(&slave->bus->bus_lock);
+ slave->status = status;
+ mutex_unlock(&slave->bus->bus_lock);
+}
+
+static int sdw_initialize_slave(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ int ret;
+ u8 val;
+
+ /*
+ * Set bus clash, parity and SCP implementation
+ * defined interrupt mask
+ * TODO: Read implementation defined interrupt mask
+ * from Slave property
+ */
+ val = SDW_SCP_INT1_IMPL_DEF | SDW_SCP_INT1_BUS_CLASH |
+ SDW_SCP_INT1_PARITY;
+
+ /* Enable SCP interrupts */
+ ret = sdw_update(slave, SDW_SCP_INTMASK1, val, val);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_SCP_INTMASK1 write failed:%d", 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->device_interrupts;
+ val |= SDW_DP0_INT_PORT_READY | SDW_DP0_INT_BRA_FAILURE;
+
+ ret = sdw_update(slave, SDW_DP0_INTMASK, val, val);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_DP0_INTMASK read failed:%d", ret);
+ return val;
+ }
+
+ return 0;
+}
+
+static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status)
+{
+ u8 clear = 0, impl_int_mask;
+ int status, status2, ret, count = 0;
+
+ status = sdw_read(slave, SDW_DP0_INT);
+ if (status < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_DP0_INT read failed:%d", status);
+ return status;
+ }
+
+ do {
+
+ if (status & SDW_DP0_INT_TEST_FAIL) {
+ dev_err(&slave->dev, "Test fail for port 0");
+ 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");
+ 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 interrupt */
+ ret = sdw_write(slave, SDW_DP0_INT, clear);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_DP0_INT write failed:%d", ret);
+ return ret;
+ }
+
+ /* Read DP0 interrupt again */
+ status2 = sdw_read(slave, SDW_DP0_INT);
+ if (status2 < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_DP0_INT read failed:%d", status2);
+ return status2;
+ }
+ status &= status2;
+
+ count++;
+
+ /* we can get alerts while processing so keep retrying */
+ } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
+
+ if (count == SDW_READ_INTR_CLEAR_RETRY)
+ dev_warn(slave->bus->dev, "Reached MAX_RETRY on DP0 read");
+
+ return ret;
+}
+
+static int sdw_handle_port_interrupt(struct sdw_slave *slave,
+ int port, u8 *slave_status)
+{
+ u8 clear = 0, 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(slave, addr);
+ if (status < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_DPN_INT read failed:%d", status);
+
+ return status;
+ }
+
+ do {
+
+ if (status & SDW_DPN_INT_TEST_FAIL) {
+ dev_err(&slave->dev, "Test fail for port:%d", 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 */
+ ret = sdw_write(slave, addr, clear);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_DPN_INT write failed:%d", ret);
+ return ret;
+ }
+
+ /* Read DPN interrupt again */
+ status2 = sdw_read(slave, addr);
+ if (status2 < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_DPN_INT read failed:%d", status2);
+ return status2;
+ }
+ status &= status2;
+
+ count++;
+
+ /* we can get alerts while processing so keep retrying */
+ } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
+
+ if (count == SDW_READ_INTR_CLEAR_RETRY)
+ dev_warn(slave->bus->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];
+ int port_num, stat, ret, count = 0;
+ unsigned long port;
+ bool slave_notify = false;
+ u8 buf, buf2[2], _buf, _buf2[2];
+
+ sdw_modify_slave_status(slave, SDW_SLAVE_ALERT);
+
+ /* Read Instat 1, Instat 2 and Instat 3 registers */
+ buf = ret = sdw_read(slave, SDW_SCP_INT1);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_SCP_INT1 read failed:%d", ret);
+ return ret;
+ }
+
+ ret = sdw_nread(slave, SDW_SCP_INTSTAT2, 2, buf2);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_SCP_INT2/3 read failed:%d", ret);
+ return ret;
+ }
+
+ do {
+ /*
+ * Check parity, bus clash and Slave (impl defined)
+ * interrupt
+ */
+ if (buf & SDW_SCP_INT1_PARITY) {
+ dev_err(&slave->dev, "Parity error detected");
+ clear |= SDW_SCP_INT1_PARITY;
+ }
+
+ if (buf & SDW_SCP_INT1_BUS_CLASH) {
+ dev_err(&slave->dev, "Bus clash error detected");
+ 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) {
+ dev_dbg(&slave->dev, "Slave impl defined interrupt\n");
+ clear |= SDW_SCP_INT1_IMPL_DEF;
+ slave_notify = true;
+ }
+
+ /* Check port 0 - 3 interrupts */
+ port = buf & SDW_SCP_INT1_PORT0_3;
+
+ /* To get port number corresponding to bits, shift it */
+ port = port >> SDW_REG_SHIFT(SDW_SCP_INT1_PORT0_3);
+ 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 + 3;
+ 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 + 10;
+ sdw_handle_port_interrupt(slave,
+ port_num,
+ &port_status[port_num]);
+ }
+ }
+
+ /* Update the Slave driver */
+ if (slave_notify && (slave->ops) &&
+ (slave->ops->interrupt_callback)) {
+ slave_intr.control_port = clear;
+ memcpy(slave_intr.port, &port_status,
+ sizeof(slave_intr.port));
+
+ slave->ops->interrupt_callback(slave, &slave_intr);
+ }
+
+ /* Ack interrupt */
+ ret = sdw_write(slave, SDW_SCP_INT1, clear);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_SCP_INT1 write failed:%d", ret);
+ return ret;
+ }
+
+ /*
+ * Read status again to ensure no new interrupts arrived
+ * while servicing interrupts.
+ */
+ _buf = ret = sdw_read(slave, SDW_SCP_INT1);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_SCP_INT1 read failed:%d", ret);
+ return ret;
+ }
+
+ ret = sdw_nread(slave, SDW_SCP_INTSTAT2, 2, _buf2);
+ if (ret < 0) {
+ dev_err(slave->bus->dev,
+ "SDW_SCP_INT2/3 read failed:%d", ret);
+ return ret;
+ }
+
+ /* Make sure no interrupts are pending */
+ buf &= _buf;
+ buf2[0] &= _buf2[0];
+ buf2[1] &= _buf2[1];
+ stat = buf || buf2[0] || buf2[1];
+
+ /*
+ * 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->bus->dev, "Reached MAX_RETRY on alert read");
+
+ return ret;
+}
+
+static int sdw_update_slave_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ if ((slave->ops) && (slave->ops->update_status))
+ return slave->ops->update_status(slave, status);
+
+ return 0;
+}
+
+/**
+ * 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;
+ int i, ret = 0;
+
+ if (status[0] == SDW_SLAVE_ATTACHED) {
+ ret = sdw_program_device_num(bus);
+ if (ret)
+ dev_err(bus->dev, "Slave attach failed: %d", ret);
+ }
+
+ /* 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;
+
+ switch (status[i]) {
+ case SDW_SLAVE_UNATTACHED:
+ if (slave->status == SDW_SLAVE_UNATTACHED)
+ break;
+
+ sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
+ break;
+
+ case SDW_SLAVE_ALERT:
+ ret = sdw_handle_slave_alerts(slave);
+ if (ret)
+ dev_err(bus->dev,
+ "Slave %d alert handling failed: %d",
+ 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;
+
+ ret = sdw_initialize_slave(slave);
+ if (ret)
+ dev_err(bus->dev,
+ "Slave %d initialization failed: %d",
+ i, ret);
+
+ break;
+
+ default:
+ dev_err(bus->dev, "Invalid slave %d status:%d",
+ i, status[i]);
+ break;
+ }
+
+ ret = sdw_update_slave_status(slave, status[i]);
+ if (ret)
+ dev_err(slave->bus->dev,
+ "Update Slave status failed:%d", ret);
+
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(sdw_handle_slave_status);
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h
new file mode 100644
index 000000000000..345c34d697e9
--- /dev/null
+++ b/drivers/soundwire/bus.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+#ifndef __SDW_BUS_H
+#define __SDW_BUS_H
+
+#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
+
+void sdw_extract_slave_id(struct sdw_bus *bus,
+ u64 addr, struct sdw_slave_id *id);
+
+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;
+};
+
+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);
+
+/* Read-Modify-Write Slave register */
+static inline 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);
+}
+
+#endif /* __SDW_BUS_H */
diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c
new file mode 100644
index 000000000000..d5f3a70c06b0
--- /dev/null
+++ b/drivers/soundwire/bus_type.c
@@ -0,0 +1,193 @@
+// 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>
+
+/**
+ * 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 = drv->id_table;
+
+ while (id && id->mfg_id) {
+ if (slave->id.mfg_id == id->mfg_id &&
+ slave->id.part_id == id->part_id)
+ return id;
+ id++;
+ }
+
+ return NULL;
+}
+
+static int sdw_bus_match(struct device *dev, struct device_driver *ddrv)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct sdw_driver *drv = drv_to_sdw_driver(ddrv);
+
+ return !!sdw_get_device_id(slave, drv);
+}
+
+int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size)
+{
+ /* modalias is sdw:m<mfg_id>p<part_id> */
+
+ return snprintf(buf, size, "sdw:m%04Xp%04X\n",
+ slave->id.mfg_id, slave->id.part_id);
+}
+
+static int sdw_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,
+ .uevent = sdw_uevent,
+};
+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;
+ int ret;
+
+ id = sdw_get_device_id(slave, drv);
+ if (!id)
+ return -ENODEV;
+
+ slave->ops = drv->ops;
+
+ /*
+ * attach to power domain but don't turn on (last arg)
+ */
+ ret = dev_pm_domain_attach(dev, false);
+ if (ret != -EPROBE_DEFER) {
+ ret = drv->probe(slave, id);
+ if (ret) {
+ dev_err(dev, "Probe of %s failed: %d\n", drv->name, ret);
+ dev_pm_domain_detach(dev, false);
+ }
+ }
+
+ if (ret)
+ return ret;
+
+ /* device is probed so let's read the properties now */
+ if (slave->ops && slave->ops->read_prop)
+ slave->ops->read_prop(slave);
+
+ /*
+ * 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);
+
+ 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;
+
+ 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)
+{
+ drv->driver.bus = &sdw_bus_type;
+
+ if (!drv->probe) {
+ pr_err("driver %s didn't provide SDW probe routine\n",
+ drv->name);
+ return -EINVAL;
+ }
+
+ drv->driver.owner = owner;
+ drv->driver.probe = sdw_drv_probe;
+
+ if (drv->remove)
+ drv->driver.remove = sdw_drv_remove;
+
+ if (drv->shutdown)
+ 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)
+{
+ return bus_register(&sdw_bus_type);
+}
+
+static void __exit sdw_bus_exit(void)
+{
+ 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 000000000000..3a9b1462039b
--- /dev/null
+++ b/drivers/soundwire/cadence_master.c
@@ -0,0 +1,751 @@
+// 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/interrupt.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include "bus.h"
+#include "cadence_master.h"
+
+#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_SSPSTAT 0xC
+#define CDNS_MCP_FRAME_SHAPE 0x10
+#define CDNS_MCP_FRAME_SHAPE_INIT 0x14
+
+#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_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_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_CMD_ERR BIT(7)
+#define CDNS_MCP_INT_RX_WL BIT(2)
+#define CDNS_MCP_INT_TXE BIT(1)
+
+#define CDNS_MCP_INTSET 0x4C
+
+#define CDNS_SDW_SLAVE_STAT 0x50
+#define CDNS_MCP_SLAVE_STAT_MASK BIT(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(30, 0)
+#define CDNS_MCP_SLAVE_INTMASK1_MASK GENMASK(16, 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
+#define CDNS_MCP_CMD_LEN 0x20
+#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_H GENMASK(23, 16)
+#define CDNS_MCP_CMD_REG_ADDR_L GENMASK(15, 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_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_DEFAULT_CLK_DIVIDER 0
+#define CDNS_DEFAULT_FRAME_SHAPE 0x30
+#define CDNS_DEFAULT_SSP_INTERVAL 0x18
+#define CDNS_TX_TIMEOUT 2000
+
+#define CDNS_PCM_PDI_OFFSET 0x2
+#define CDNS_PDM_PDI_OFFSET 0x6
+
+#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_clear_bit(struct sdw_cdns *cdns, int offset, u32 value)
+{
+ int timeout = 10;
+ u32 reg_read;
+
+ writel(value, cdns->registers + offset);
+
+ /* Wait for bit to be self cleared */
+ do {
+ reg_read = readl(cdns->registers + offset);
+ if ((reg_read & value) == 0)
+ return 0;
+
+ timeout--;
+ udelay(50);
+ } while (timeout != 0);
+
+ return -EAGAIN;
+}
+
+/*
+ * 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_dbg(cdns->dev, "Msg Ack not received\n");
+ if (cdns->response_buf[i] & CDNS_MCP_RESP_NACK) {
+ nack = 1;
+ dev_err(cdns->dev, "Msg NACK received\n");
+ }
+ }
+ }
+
+ if (nack) {
+ dev_err(cdns->dev, "Msg NACKed for Slave %d\n", msg->dev_num);
+ return SDW_CMD_FAIL;
+ } else if (no_ack) {
+ dev_dbg(cdns->dev, "Msg ignored for Slave %d\n", msg->dev_num);
+ return SDW_CMD_IGNORED;
+ }
+
+ /* fill response */
+ for (i = 0; i < count; i++)
+ msg->buf[i + offset] = cdns->response_buf[i] >>
+ SDW_REG_SHIFT(CDNS_MCP_RESP_RDATA);
+
+ return SDW_CMD_OK;
+}
+
+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;
+
+ for (i = 0; i < count; i++) {
+ data = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR);
+ data |= cmd << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND);
+ data |= addr++ << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
+
+ if (msg->flags == SDW_MSG_FLAG_WRITE)
+ data |= msg->buf[i + offset];
+
+ data |= msg->ssp_sync << SDW_REG_SHIFT(CDNS_MCP_CMD_SSP_TAG);
+ 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\n");
+ msg->len = 0;
+ 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] = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR);
+ data[0] |= 0x3 << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND);
+ data[1] = data[0];
+
+ data[0] |= SDW_SCP_ADDRPAGE1 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
+ data[1] |= SDW_SCP_ADDRPAGE2 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
+
+ data[0] |= 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");
+ if (cdns->response_buf[i] & CDNS_MCP_RESP_NACK) {
+ nack = 1;
+ dev_err(cdns->dev, "Program SCP NACK received");
+ }
+ }
+ }
+
+ /* For NACK, NO ack, don't return err if we are in Broadcast mode */
+ if (nack) {
+ dev_err(cdns->dev,
+ "SCP_addrpage NACKed for Slave %d", msg->dev_num);
+ return SDW_CMD_FAIL;
+ } else if (no_ack) {
+ dev_dbg(cdns->dev,
+ "SCP_addrpage ignored for Slave %d", 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;
+}
+
+static 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 < 0)
+ goto exit;
+ }
+
+ if (!(msg->len % CDNS_MCP_CMD_LEN))
+ goto exit;
+
+ ret = _cdns_xfer_msg(cdns, msg, cmd, i * CDNS_MCP_CMD_LEN,
+ msg->len % CDNS_MCP_CMD_LEN, false);
+
+exit:
+ return ret;
+}
+
+static 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);
+}
+
+static 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);
+}
+
+/*
+ * IRQ handling
+ */
+
+static void cdns_read_response(struct sdw_cdns *cdns)
+{
+ u32 num_resp, cmd_base;
+ int i;
+
+ num_resp = cdns_readl(cdns, CDNS_MCP_FIFOSTAT);
+ num_resp &= CDNS_MCP_RX_FIFO_AVAIL;
+
+ 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 int cdns_update_slave_status(struct sdw_cdns *cdns,
+ u32 slave0, u32 slave1)
+{
+ enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
+ bool is_slave = false;
+ u64 slave, mask;
+ int i, set_status;
+
+ /* combine the two status */
+ slave = ((u64)slave1 << 32) | slave0;
+ memset(status, 0, sizeof(status));
+
+ for (i = 0; i <= SDW_MAX_DEVICES; i++) {
+ mask = (slave >> (i * CDNS_MCP_SLAVE_STATUS_NUM)) &
+ CDNS_MCP_SLAVE_STATUS_BITS;
+ if (!mask)
+ continue;
+
+ is_slave = true;
+ set_status = 0;
+
+ 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++;
+ }
+
+ /* first check if Slave reported multiple status */
+ if (set_status > 1) {
+ dev_warn(cdns->dev,
+ "Slave reported multiple Status: %d\n",
+ status[i]);
+ /*
+ * TODO: we need to reread the status here by
+ * issuing a PING cmd
+ */
+ }
+ }
+
+ 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;
+ int ret = IRQ_HANDLED;
+
+ /* Check if the link is up */
+ if (!cdns->link_up)
+ return IRQ_NONE;
+
+ int_status = cdns_readl(cdns, CDNS_MCP_INTSTAT);
+
+ 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_CTRL_CLASH) {
+
+ /* Slave is driving bit slot during control word */
+ dev_err_ratelimited(cdns->dev, "Bus clash for control word\n");
+ int_status |= CDNS_MCP_INT_CTRL_CLASH;
+ }
+
+ 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");
+ int_status |= CDNS_MCP_INT_DATA_CLASH;
+ }
+
+ 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;
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ cdns_writel(cdns, CDNS_MCP_INTSTAT, int_status);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_cdns_irq);
+
+/**
+ * sdw_cdns_thread() - Cadence irq thread handler
+ * @irq: irq number
+ * @dev_id: irq context
+ */
+irqreturn_t sdw_cdns_thread(int irq, void *dev_id)
+{
+ struct sdw_cdns *cdns = dev_id;
+ u32 slave0, slave1;
+
+ dev_dbg(cdns->dev, "Slave status change\n");
+
+ slave0 = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT0);
+ slave1 = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT1);
+
+ cdns_update_slave_status(cdns, slave0, slave1);
+ cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT0, slave0);
+ cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT1, slave1);
+
+ /* clear and unmask Slave interrupt now */
+ cdns_writel(cdns, CDNS_MCP_INTSTAT, CDNS_MCP_INT_SLAVE_MASK);
+ cdns_updatel(cdns, CDNS_MCP_INTMASK,
+ CDNS_MCP_INT_SLAVE_MASK, CDNS_MCP_INT_SLAVE_MASK);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(sdw_cdns_thread);
+
+/*
+ * init routines
+ */
+static int _cdns_enable_interrupt(struct sdw_cdns *cdns)
+{
+ u32 mask;
+
+ cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK0,
+ CDNS_MCP_SLAVE_INTMASK0_MASK);
+ cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK1,
+ CDNS_MCP_SLAVE_INTMASK1_MASK);
+
+ mask = CDNS_MCP_INT_SLAVE_RSVD | CDNS_MCP_INT_SLAVE_ALERT |
+ CDNS_MCP_INT_SLAVE_ATTACH | CDNS_MCP_INT_SLAVE_NATTACH |
+ CDNS_MCP_INT_CTRL_CLASH | CDNS_MCP_INT_DATA_CLASH |
+ CDNS_MCP_INT_RX_WL | CDNS_MCP_INT_IRQ | CDNS_MCP_INT_DPINT;
+
+ cdns_writel(cdns, CDNS_MCP_INTMASK, mask);
+
+ return 0;
+}
+
+/**
+ * sdw_cdns_enable_interrupt() - Enable SDW interrupts and update config
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns)
+{
+ int ret;
+
+ _cdns_enable_interrupt(cdns);
+ ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE,
+ CDNS_MCP_CONFIG_UPDATE_BIT);
+ if (ret < 0)
+ dev_err(cdns->dev, "Config update timedout");
+
+ return ret;
+}
+EXPORT_SYMBOL(sdw_cdns_enable_interrupt);
+
+/**
+ * sdw_cdns_init() - Cadence initialization
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_init(struct sdw_cdns *cdns)
+{
+ u32 val;
+ int ret;
+
+ /* Exit clock stop */
+ ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL,
+ CDNS_MCP_CONTROL_CLK_STOP_CLR);
+ if (ret < 0) {
+ dev_err(cdns->dev, "Couldn't exit from clock stop\n");
+ return ret;
+ }
+
+ /* Set clock divider */
+ val = cdns_readl(cdns, CDNS_MCP_CLK_CTRL0);
+ val |= CDNS_DEFAULT_CLK_DIVIDER;
+ cdns_writel(cdns, CDNS_MCP_CLK_CTRL0, val);
+
+ /* Set the default frame shape */
+ cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, CDNS_DEFAULT_FRAME_SHAPE);
+
+ /* Set SSP interval to default value */
+ cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, CDNS_DEFAULT_SSP_INTERVAL);
+ cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, CDNS_DEFAULT_SSP_INTERVAL);
+
+ /* 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);
+
+ /* Set Max cmd retry to 15 */
+ val |= CDNS_MCP_CONFIG_MCMD_RETRY;
+
+ /* Set frame delay between PREQ and ping frame to 15 frames */
+ val |= 0xF << SDW_REG_SHIFT(CDNS_MCP_CONFIG_MPREQ_DELAY);
+
+ /* Disable auto bus release */
+ val &= ~CDNS_MCP_CONFIG_BUS_REL;
+
+ /* Disable sniffer mode */
+ val &= ~CDNS_MCP_CONFIG_SNIFFER;
+
+ /* Set cmd mode for Tx and Rx cmds */
+ val &= ~CDNS_MCP_CONFIG_CMD;
+
+ /* Set operation to normal */
+ val &= ~CDNS_MCP_CONFIG_OP;
+ val |= CDNS_MCP_CONFIG_OP_NORMAL;
+
+ cdns_writel(cdns, CDNS_MCP_CONFIG, val);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_init);
+
+struct sdw_master_ops sdw_cdns_master_ops = {
+ .read_prop = sdw_master_read_prop,
+ .xfer_msg = cdns_xfer_msg,
+ .xfer_msg_defer = cdns_xfer_msg_defer,
+ .reset_page_addr = cdns_reset_page_addr,
+};
+EXPORT_SYMBOL(sdw_cdns_master_ops);
+
+/**
+ * sdw_cdns_probe() - Cadence probe routine
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_probe(struct sdw_cdns *cdns)
+{
+ init_completion(&cdns->tx_complete);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_probe);
+
+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 000000000000..beaf6c9804eb
--- /dev/null
+++ b/drivers/soundwire/cadence_master.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+#ifndef __SDW_CADENCE_H
+#define __SDW_CADENCE_H
+
+/**
+ * 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
+ * @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;
+
+ u32 response_buf[0x80];
+ struct completion tx_complete;
+ struct sdw_defer *defer;
+
+ void __iomem *registers;
+
+ bool link_up;
+ unsigned int msg_count;
+};
+
+#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_enable_interrupt(struct sdw_cdns *cdns);
+
+
+#endif /* __SDW_CADENCE_H */
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c
new file mode 100644
index 000000000000..86a7bd1fc912
--- /dev/null
+++ b/drivers/soundwire/intel.c
@@ -0,0 +1,345 @@
+// 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/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
+#include "cadence_master.h"
+#include "intel.h"
+
+/* Intel SHIM Registers Definition */
+#define SDW_SHIM_LCAP 0x0
+#define SDW_SHIM_LCTL 0x4
+#define SDW_SHIM_IPPTR 0x8
+#define SDW_SHIM_SYNC 0xC
+
+#define SDW_SHIM_CTLSCAP(x) (0x010 + 0x60 * x)
+#define SDW_SHIM_CTLS0CM(x) (0x012 + 0x60 * x)
+#define SDW_SHIM_CTLS1CM(x) (0x014 + 0x60 * x)
+#define SDW_SHIM_CTLS2CM(x) (0x016 + 0x60 * x)
+#define SDW_SHIM_CTLS3CM(x) (0x018 + 0x60 * x)
+#define SDW_SHIM_PCMSCAP(x) (0x020 + 0x60 * x)
+
+#define SDW_SHIM_PCMSYCHM(x, y) (0x022 + (0x60 * x) + (0x2 * y))
+#define SDW_SHIM_PCMSYCHC(x, y) (0x042 + (0x60 * x) + (0x2 * y))
+#define SDW_SHIM_PDMSCAP(x) (0x062 + 0x60 * x)
+#define SDW_SHIM_IOCTL(x) (0x06C + 0x60 * x)
+#define SDW_SHIM_CTMCTL(x) (0x06E + 0x60 * x)
+
+#define SDW_SHIM_WAKEEN 0x190
+#define SDW_SHIM_WAKESTS 0x192
+
+#define SDW_SHIM_LCTL_SPA BIT(0)
+#define SDW_SHIM_LCTL_CPA BIT(8)
+
+#define SDW_SHIM_SYNC_SYNCPRD_VAL 0x176F
+#define SDW_SHIM_SYNC_SYNCPRD GENMASK(14, 0)
+#define SDW_SHIM_SYNC_SYNCCPU BIT(15)
+#define SDW_SHIM_SYNC_CMDSYNC_MASK GENMASK(19, 16)
+#define SDW_SHIM_SYNC_CMDSYNC BIT(16)
+#define SDW_SHIM_SYNC_SYNCGO BIT(24)
+
+#define SDW_SHIM_PCMSCAP_ISS GENMASK(3, 0)
+#define SDW_SHIM_PCMSCAP_OSS GENMASK(7, 4)
+#define SDW_SHIM_PCMSCAP_BSS GENMASK(12, 8)
+
+#define SDW_SHIM_PCMSYCM_LCHN GENMASK(3, 0)
+#define SDW_SHIM_PCMSYCM_HCHN GENMASK(7, 4)
+#define SDW_SHIM_PCMSYCM_STREAM GENMASK(13, 8)
+#define SDW_SHIM_PCMSYCM_DIR BIT(15)
+
+#define SDW_SHIM_PDMSCAP_ISS GENMASK(3, 0)
+#define SDW_SHIM_PDMSCAP_OSS GENMASK(7, 4)
+#define SDW_SHIM_PDMSCAP_BSS GENMASK(12, 8)
+#define SDW_SHIM_PDMSCAP_CPSS GENMASK(15, 13)
+
+#define SDW_SHIM_IOCTL_MIF BIT(0)
+#define SDW_SHIM_IOCTL_CO BIT(1)
+#define SDW_SHIM_IOCTL_COE BIT(2)
+#define SDW_SHIM_IOCTL_DO BIT(3)
+#define SDW_SHIM_IOCTL_DOE BIT(4)
+#define SDW_SHIM_IOCTL_BKE BIT(5)
+#define SDW_SHIM_IOCTL_WPDD BIT(6)
+#define SDW_SHIM_IOCTL_CIBD BIT(8)
+#define SDW_SHIM_IOCTL_DIBD BIT(9)
+
+#define SDW_SHIM_CTMCTL_DACTQE BIT(0)
+#define SDW_SHIM_CTMCTL_DODS BIT(1)
+#define SDW_SHIM_CTMCTL_DOAIS GENMASK(4, 3)
+
+#define SDW_SHIM_WAKEEN_ENABLE BIT(0)
+#define SDW_SHIM_WAKESTS_STATUS BIT(0)
+
+/* Intel ALH Register definitions */
+#define SDW_ALH_STRMZCFG(x) (0x000 + (0x4 * x))
+
+#define SDW_ALH_STRMZCFG_DMAT_VAL 0x3
+#define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0)
+#define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16)
+
+struct sdw_intel {
+ struct sdw_cdns cdns;
+ int instance;
+ struct sdw_intel_link_res *res;
+};
+
+#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_clear_bit(void __iomem *base, int offset, u32 value, u32 mask)
+{
+ int timeout = 10;
+ u32 reg_read;
+
+ writel(value, base + offset);
+ do {
+ reg_read = readl(base + offset);
+ if (!(reg_read & mask))
+ return 0;
+
+ timeout--;
+ udelay(50);
+ } while (timeout != 0);
+
+ return -EAGAIN;
+}
+
+static int intel_set_bit(void __iomem *base, int offset, u32 value, u32 mask)
+{
+ int timeout = 10;
+ u32 reg_read;
+
+ writel(value, base + offset);
+ do {
+ reg_read = readl(base + offset);
+ if (reg_read & mask)
+ return 0;
+
+ timeout--;
+ udelay(50);
+ } while (timeout != 0);
+
+ return -EAGAIN;
+}
+
+/*
+ * shim ops
+ */
+
+static int intel_link_power_up(struct sdw_intel *sdw)
+{
+ unsigned int link_id = sdw->instance;
+ void __iomem *shim = sdw->res->shim;
+ int spa_mask, cpa_mask;
+ int link_control, ret;
+
+ /* Link power up sequence */
+ link_control = intel_readl(shim, SDW_SHIM_LCTL);
+ spa_mask = (SDW_SHIM_LCTL_SPA << link_id);
+ cpa_mask = (SDW_SHIM_LCTL_CPA << link_id);
+ link_control |= spa_mask;
+
+ ret = intel_set_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask);
+ if (ret < 0)
+ return ret;
+
+ sdw->cdns.link_up = true;
+ return 0;
+}
+
+static int intel_shim_init(struct sdw_intel *sdw)
+{
+ void __iomem *shim = sdw->res->shim;
+ unsigned int link_id = sdw->instance;
+ int sync_reg, ret;
+ u16 ioctl = 0, act = 0;
+
+ /* Initialize Shim */
+ ioctl |= SDW_SHIM_IOCTL_BKE;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+
+ ioctl |= SDW_SHIM_IOCTL_WPDD;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+
+ ioctl |= SDW_SHIM_IOCTL_DO;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+
+ ioctl |= SDW_SHIM_IOCTL_DOE;
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), 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);
+
+ ioctl &= ~(SDW_SHIM_IOCTL_DO);
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+
+ ioctl |= (SDW_SHIM_IOCTL_MIF);
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+
+ ioctl &= ~(SDW_SHIM_IOCTL_BKE);
+ ioctl &= ~(SDW_SHIM_IOCTL_COE);
+
+ intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+
+ act |= 0x1 << SDW_REG_SHIFT(SDW_SHIM_CTMCTL_DOAIS);
+ act |= SDW_SHIM_CTMCTL_DACTQE;
+ act |= SDW_SHIM_CTMCTL_DODS;
+ intel_writew(shim, SDW_SHIM_CTMCTL(link_id), act);
+
+ /* Now set SyncPRD period */
+ sync_reg = intel_readl(shim, SDW_SHIM_SYNC);
+ sync_reg |= (SDW_SHIM_SYNC_SYNCPRD_VAL <<
+ SDW_REG_SHIFT(SDW_SHIM_SYNC_SYNCPRD));
+
+ /* Set SyncCPU bit */
+ sync_reg |= SDW_SHIM_SYNC_SYNCCPU;
+ ret = intel_clear_bit(shim, SDW_SHIM_SYNC, sync_reg,
+ SDW_SHIM_SYNC_SYNCCPU);
+ if (ret < 0)
+ dev_err(sdw->cdns.dev, "Failed to set sync period: %d", ret);
+
+ return ret;
+}
+
+static int intel_prop_read(struct sdw_bus *bus)
+{
+ /* Initialize with default handler to read all DisCo properties */
+ sdw_master_read_prop(bus);
+
+ /* BIOS is not giving some values correctly. So, lets override them */
+ bus->prop.num_freq = 1;
+ bus->prop.freq = devm_kcalloc(bus->dev, sizeof(*bus->prop.freq),
+ bus->prop.num_freq, GFP_KERNEL);
+ if (!bus->prop.freq)
+ return -ENOMEM;
+
+ bus->prop.freq[0] = bus->prop.max_freq;
+ bus->prop.err_threshold = 5;
+
+ return 0;
+}
+
+/*
+ * probe and init
+ */
+static int intel_probe(struct platform_device *pdev)
+{
+ struct sdw_intel *sdw;
+ int ret;
+
+ sdw = devm_kzalloc(&pdev->dev, sizeof(*sdw), GFP_KERNEL);
+ if (!sdw)
+ return -ENOMEM;
+
+ sdw->instance = pdev->id;
+ sdw->res = dev_get_platdata(&pdev->dev);
+ sdw->cdns.dev = &pdev->dev;
+ sdw->cdns.registers = sdw->res->registers;
+ sdw->cdns.instance = sdw->instance;
+ sdw->cdns.msg_count = 0;
+ sdw->cdns.bus.dev = &pdev->dev;
+ sdw->cdns.bus.link_id = pdev->id;
+
+ sdw_cdns_probe(&sdw->cdns);
+
+ /* Set property read ops */
+ sdw_cdns_master_ops.read_prop = intel_prop_read;
+ sdw->cdns.bus.ops = &sdw_cdns_master_ops;
+
+ platform_set_drvdata(pdev, sdw);
+
+ ret = sdw_add_bus_master(&sdw->cdns.bus);
+ if (ret) {
+ dev_err(&pdev->dev, "sdw_add_bus_master fail: %d\n", ret);
+ goto err_master_reg;
+ }
+
+ /* Initialize shim and controller */
+ intel_link_power_up(sdw);
+ intel_shim_init(sdw);
+
+ ret = sdw_cdns_init(&sdw->cdns);
+ if (ret)
+ goto err_init;
+
+ ret = sdw_cdns_enable_interrupt(&sdw->cdns);
+ if (ret)
+ goto err_init;
+
+ /* Acquire IRQ */
+ ret = request_threaded_irq(sdw->res->irq, sdw_cdns_irq,
+ sdw_cdns_thread, IRQF_SHARED, KBUILD_MODNAME,
+ &sdw->cdns);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "unable to grab IRQ %d, disabling device\n",
+ sdw->res->irq);
+ goto err_init;
+ }
+
+ return 0;
+
+err_init:
+ sdw_delete_bus_master(&sdw->cdns.bus);
+err_master_reg:
+ return ret;
+}
+
+static int intel_remove(struct platform_device *pdev)
+{
+ struct sdw_intel *sdw;
+
+ sdw = platform_get_drvdata(pdev);
+
+ free_irq(sdw->res->irq, sdw);
+ sdw_delete_bus_master(&sdw->cdns.bus);
+
+ return 0;
+}
+
+static struct platform_driver sdw_intel_drv = {
+ .probe = intel_probe,
+ .remove = intel_remove,
+ .driver = {
+ .name = "int-sdw",
+
+ },
+};
+
+module_platform_driver(sdw_intel_drv);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("platform:int-sdw");
+MODULE_DESCRIPTION("Intel Soundwire Master Driver");
diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h
new file mode 100644
index 000000000000..ffa30d9535a2
--- /dev/null
+++ b/drivers/soundwire/intel.h
@@ -0,0 +1,23 @@
+// 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_res - Soundwire link resources
+ * @registers: Link IO registers base
+ * @shim: Audio shim pointer
+ * @alh: ALH (Audio Link Hub) pointer
+ * @irq: Interrupt line
+ *
+ * This is set as pdata for each link instance.
+ */
+struct sdw_intel_link_res {
+ void __iomem *registers;
+ void __iomem *shim;
+ void __iomem *alh;
+ int irq;
+};
+
+#endif /* __SDW_INTEL_LOCAL_H */
diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c
new file mode 100644
index 000000000000..6f2bb99526f2
--- /dev/null
+++ b/drivers/soundwire/intel_init.c
@@ -0,0 +1,198 @@
+// 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/platform_device.h>
+#include <linux/soundwire/sdw_intel.h>
+#include "intel.h"
+
+#define SDW_MAX_LINKS 4
+#define SDW_SHIM_LCAP 0x0
+#define SDW_SHIM_BASE 0x2C000
+#define SDW_ALH_BASE 0x2C800
+#define SDW_LINK_BASE 0x30000
+#define SDW_LINK_SIZE 0x10000
+
+struct sdw_link_data {
+ struct sdw_intel_link_res res;
+ struct platform_device *pdev;
+};
+
+struct sdw_intel_ctx {
+ int count;
+ struct sdw_link_data *links;
+};
+
+static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx)
+{
+ struct sdw_link_data *link = ctx->links;
+ int i;
+
+ if (!link)
+ return 0;
+
+ for (i = 0; i < ctx->count; i++) {
+ if (link->pdev)
+ platform_device_unregister(link->pdev);
+ link++;
+ }
+
+ kfree(ctx->links);
+ ctx->links = NULL;
+
+ return 0;
+}
+
+static struct sdw_intel_ctx
+*sdw_intel_add_controller(struct sdw_intel_res *res)
+{
+ struct platform_device_info pdevinfo;
+ struct platform_device *pdev;
+ struct sdw_link_data *link;
+ struct sdw_intel_ctx *ctx;
+ struct acpi_device *adev;
+ int ret, i;
+ u8 count;
+ u32 caps;
+
+ if (acpi_bus_get_device(res->handle, &adev))
+ return NULL;
+
+ /* Found controller, find links supported */
+ count = 0;
+ ret = fwnode_property_read_u8_array(acpi_fwnode_handle(adev),
+ "mipi-sdw-master-count", &count, 1);
+
+ /* Don't fail on error, continue and use hw value */
+ if (ret) {
+ dev_err(&adev->dev,
+ "Failed to read mipi-sdw-master-count: %d\n", ret);
+ count = SDW_MAX_LINKS;
+ }
+
+ /* Check SNDWLCAP.LCOUNT */
+ caps = ioread32(res->mmio_base + SDW_SHIM_BASE + SDW_SHIM_LCAP);
+
+ /* Check HW supported vs property value and use min of two */
+ count = min_t(u8, caps, count);
+
+ /* Check count is within bounds */
+ if (count > SDW_MAX_LINKS) {
+ dev_err(&adev->dev, "Link count %d exceeds max %d\n",
+ count, SDW_MAX_LINKS);
+ return NULL;
+ }
+
+ dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count);
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return NULL;
+
+ ctx->count = count;
+ ctx->links = kcalloc(ctx->count, sizeof(*ctx->links), GFP_KERNEL);
+ if (!ctx->links)
+ goto link_err;
+
+ link = ctx->links;
+
+ /* Create SDW Master devices */
+ for (i = 0; i < count; i++) {
+
+ link->res.irq = res->irq;
+ link->res.registers = res->mmio_base + SDW_LINK_BASE
+ + (SDW_LINK_SIZE * i);
+ link->res.shim = res->mmio_base + SDW_SHIM_BASE;
+ link->res.alh = res->mmio_base + SDW_ALH_BASE;
+
+ memset(&pdevinfo, 0, sizeof(pdevinfo));
+
+ pdevinfo.parent = res->parent;
+ pdevinfo.name = "int-sdw";
+ pdevinfo.id = i;
+ pdevinfo.fwnode = acpi_fwnode_handle(adev);
+ pdevinfo.data = &link->res;
+ pdevinfo.size_data = sizeof(link->res);
+
+ pdev = platform_device_register_full(&pdevinfo);
+ if (IS_ERR(pdev)) {
+ dev_err(&adev->dev,
+ "platform device creation failed: %ld\n",
+ PTR_ERR(pdev));
+ goto pdev_err;
+ }
+
+ link->pdev = pdev;
+ link++;
+ }
+
+ return ctx;
+
+pdev_err:
+ sdw_intel_cleanup_pdev(ctx);
+link_err:
+ kfree(ctx);
+ return NULL;
+}
+
+static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level,
+ void *cdata, void **return_value)
+{
+ struct sdw_intel_res *res = cdata;
+ struct acpi_device *adev;
+
+ if (acpi_bus_get_device(handle, &adev)) {
+ dev_err(&adev->dev, "Couldn't find ACPI handle\n");
+ return AE_NOT_FOUND;
+ }
+
+ res->handle = handle;
+ return AE_OK;
+}
+
+/**
+ * sdw_intel_init() - SoundWire Intel init routine
+ * @parent_handle: ACPI parent handle
+ * @res: resource data
+ *
+ * This scans the namespace and creates SoundWire link controller devices
+ * based on the info queried.
+ */
+void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res)
+{
+ acpi_status status;
+
+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE,
+ parent_handle, 1,
+ sdw_intel_acpi_cb,
+ NULL, res, NULL);
+ if (ACPI_FAILURE(status))
+ return NULL;
+
+ return sdw_intel_add_controller(res);
+}
+EXPORT_SYMBOL(sdw_intel_init);
+
+/**
+ * sdw_intel_exit() - SoundWire Intel exit
+ * @arg: callback context
+ *
+ * Delete the controller instances created and cleanup
+ */
+void sdw_intel_exit(void *arg)
+{
+ struct sdw_intel_ctx *ctx = arg;
+
+ sdw_intel_cleanup_pdev(ctx);
+ kfree(ctx);
+}
+EXPORT_SYMBOL(sdw_intel_exit);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Intel Soundwire Init Library");
diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c
new file mode 100644
index 000000000000..fdeba0c3b589
--- /dev/null
+++ b/drivers/soundwire/mipi_disco.c
@@ -0,0 +1,401 @@
+// 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-master-%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") == true)
+ prop->clk_stop_mode = SDW_CLK_STOP_MODE0;
+
+ if (fwnode_property_read_bool(link,
+ "mipi-sdw-clock-stop-mode1-supported") == true)
+ prop->clk_stop_mode |= SDW_CLK_STOP_MODE1;
+
+ fwnode_property_read_u32(link,
+ "mipi-sdw-max-clock-frequency", &prop->max_freq);
+
+ nval = fwnode_property_read_u32_array(link,
+ "mipi-sdw-clock-frequencies-supported", NULL, 0);
+ if (nval > 0) {
+
+ prop->num_freq = nval;
+ prop->freq = devm_kcalloc(bus->dev, prop->num_freq,
+ sizeof(*prop->freq), GFP_KERNEL);
+ if (!prop->freq)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(link,
+ "mipi-sdw-clock-frequencies-supported",
+ prop->freq, prop->num_freq);
+ }
+
+ /*
+ * Check the frequencies supported. If FW doesn't provide max
+ * freq, then populate here by checking values.
+ */
+ if (!prop->max_freq && prop->freq) {
+ prop->max_freq = prop->freq[0];
+ for (i = 1; i < prop->num_freq; i++) {
+ if (prop->freq[i] > prop->max_freq)
+ prop->max_freq = prop->freq[i];
+ }
+ }
+
+ nval = fwnode_property_read_u32_array(link,
+ "mipi-sdw-supported-clock-gears", NULL, 0);
+ 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_read_u32_array(port,
+ "mipi-sdw-port-wordlength-configs", NULL, 0);
+ 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->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->device_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_read_u32_array(node,
+ "mipi-sdw-port-wordlength-configs", NULL, 0);
+ 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].device_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_read_u32_array(node,
+ "mipi-sdw-channel-number-list", NULL, 0);
+ if (nval > 0) {
+
+ dpn[i].num_ch = nval;
+ dpn[i].ch = devm_kcalloc(&slave->dev, dpn[i].num_ch,
+ sizeof(*dpn[i].ch), GFP_KERNEL);
+ if (!dpn[i].ch)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(node,
+ "mipi-sdw-channel-number-list",
+ dpn[i].ch, dpn[i].num_ch);
+ }
+
+ nval = fwnode_property_read_u32_array(node,
+ "mipi-sdw-channel-combination-list", NULL, 0);
+ 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 num_of_ports, nval, i, dp0 = 0;
+
+ 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);
+ dp0 = 1;
+ }
+
+ /*
+ * 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");
+
+ /* some ports are bidirectional so check total ports by ORing */
+ nval = prop->source_ports | prop->sink_ports;
+ num_of_ports = hweight32(nval) + dp0; /* add DP0 */
+
+ /* Allocate port_ready based on num_of_ports */
+ slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports,
+ sizeof(*slave->port_ready), GFP_KERNEL);
+ if (!slave->port_ready)
+ return -ENOMEM;
+
+ /* Initialize completion */
+ for (i = 0; i < num_of_ports; i++)
+ init_completion(&slave->port_ready[i]);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_slave_read_prop);
diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c
new file mode 100644
index 000000000000..ac103bd0c176
--- /dev/null
+++ b/drivers/soundwire/slave.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-17 Intel Corporation.
+
+#include <linux/acpi.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+
+static void sdw_slave_release(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+
+ kfree(slave);
+}
+
+static int sdw_slave_add(struct sdw_bus *bus,
+ struct sdw_slave_id *id, struct fwnode_handle *fwnode)
+{
+ struct sdw_slave *slave;
+ int ret;
+
+ 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;
+
+ /* name shall be sdw:link:mfg:part:class:unique */
+ dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x",
+ bus->link_id, id->mfg_id, id->part_id,
+ id->class_id, id->unique_id);
+
+ slave->dev.release = sdw_slave_release;
+ slave->dev.bus = &sdw_bus_type;
+ slave->bus = bus;
+ slave->status = SDW_SLAVE_UNATTACHED;
+ slave->dev_num = 0;
+
+ 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;
+}
+
+#if IS_ENABLED(CONFIG_ACPI)
+/*
+ * 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 *adev, *parent;
+
+ parent = ACPI_COMPANION(bus->dev);
+ if (!parent) {
+ dev_err(bus->dev, "Can't find parent for acpi bind\n");
+ return -ENODEV;
+ }
+
+ list_for_each_entry(adev, &parent->children, node) {
+ unsigned long long addr;
+ struct sdw_slave_id id;
+ 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 status;
+ }
+
+ /* Extract link id from ADR, Bit 51 to 48 (included) */
+ link_id = (addr >> 48) & GENMASK(3, 0);
+
+ /* Check for link_id match */
+ if (link_id != bus->link_id)
+ continue;
+
+ sdw_extract_slave_id(bus, addr, &id);
+
+ /*
+ * don't error check for sdw_slave_add as we want to continue
+ * adding Slaves
+ */
+ sdw_slave_add(bus, &id, acpi_fwnode_handle(adev));
+ }
+
+ return 0;
+}
+
+#endif