summaryrefslogtreecommitdiff
path: root/drivers/soundwire
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soundwire')
-rw-r--r--drivers/soundwire/Kconfig41
-rw-r--r--drivers/soundwire/Makefile32
-rw-r--r--drivers/soundwire/amd_init.c231
-rw-r--r--drivers/soundwire/amd_init.h21
-rw-r--r--drivers/soundwire/amd_manager.c1385
-rw-r--r--drivers/soundwire/amd_manager.h277
-rw-r--r--drivers/soundwire/bus.c1572
-rw-r--r--drivers/soundwire/bus.h130
-rw-r--r--drivers/soundwire/bus_type.c123
-rw-r--r--drivers/soundwire/cadence_master.c2332
-rw-r--r--drivers/soundwire/cadence_master.h162
-rw-r--r--drivers/soundwire/debugfs.c372
-rw-r--r--drivers/soundwire/dmi-quirks.c179
-rw-r--r--drivers/soundwire/generic_bandwidth_allocation.c689
-rw-r--r--drivers/soundwire/intel.c1422
-rw-r--r--drivers/soundwire/intel.h257
-rw-r--r--drivers/soundwire/intel_ace2x.c1072
-rw-r--r--drivers/soundwire/intel_ace2x_debugfs.c153
-rw-r--r--drivers/soundwire/intel_auxdevice.c871
-rw-r--r--drivers/soundwire/intel_auxdevice.h19
-rw-r--r--drivers/soundwire/intel_bus_common.c286
-rw-r--r--drivers/soundwire/intel_init.c443
-rw-r--r--drivers/soundwire/irq.c63
-rw-r--r--drivers/soundwire/irq.h38
-rw-r--r--drivers/soundwire/master.c188
-rw-r--r--drivers/soundwire/mipi_disco.c390
-rw-r--r--drivers/soundwire/qcom.c1789
-rw-r--r--drivers/soundwire/slave.c224
-rw-r--r--drivers/soundwire/stream.c1786
-rw-r--r--drivers/soundwire/sysfs_local.h20
-rw-r--r--drivers/soundwire/sysfs_slave.c266
-rw-r--r--drivers/soundwire/sysfs_slave_dpn.c304
32 files changed, 14754 insertions, 2383 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig
index 19c8efb9a5ee..ad56393e4c93 100644
--- a/drivers/soundwire/Kconfig
+++ b/drivers/soundwire/Kconfig
@@ -1,10 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
#
# SoundWire subsystem configuration
#
menuconfig SOUNDWIRE
- bool "SoundWire support"
- ---help---
+ tristate "SoundWire support"
+ depends on ACPI || OF
+ depends on SND_SOC_SDCA_OPTIONAL
+ 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
@@ -16,22 +19,44 @@ if SOUNDWIRE
comment "SoundWire Devices"
-config SOUNDWIRE_BUS
- tristate
- select REGMAP_SOUNDWIRE
+config SOUNDWIRE_AMD
+ tristate "AMD SoundWire Manager driver"
+ select SOUNDWIRE_GENERIC_ALLOCATION
+ depends on ACPI && SND_SOC
+ help
+ SoundWire AMD Manager driver.
+ If you have an AMD platform which has a SoundWire Manager then
+ enable this config option to get the SoundWire support for that
+ device.
config SOUNDWIRE_CADENCE
tristate
+ select CRC8
config SOUNDWIRE_INTEL
tristate "Intel SoundWire Master driver"
select SOUNDWIRE_CADENCE
- select SOUNDWIRE_BUS
- depends on X86 && ACPI && SND_SOC
- ---help---
+ select SOUNDWIRE_GENERIC_ALLOCATION
+ select AUXILIARY_BUS
+ depends on ACPI && SND_SOC
+ depends on SND_SOC_SOF_HDA_MLINK || !SND_SOC_SOF_HDA_MLINK
+ 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
index 5817beaca0e1..e80a2c2cf3e7 100644
--- a/drivers/soundwire/Makefile
+++ b/drivers/soundwire/Makefile
@@ -1,18 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for soundwire core
#
#Bus Objs
-soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o stream.o
-obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o
+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
+
+ifdef CONFIG_IRQ_DOMAIN
+soundwire-bus-y += irq.o
+endif
+
+#AMD driver
+soundwire-amd-y := amd_init.o amd_manager.o
+obj-$(CONFIG_SOUNDWIRE_AMD) += soundwire-amd.o
#Cadence Objs
-soundwire-cadence-objs := cadence_master.o
+soundwire-cadence-y := cadence_master.o
obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o
#Intel driver
-soundwire-intel-objs := intel.o
+soundwire-intel-y := intel.o intel_ace2x.o intel_ace2x_debugfs.o \
+ intel_auxdevice.o intel_init.o dmi-quirks.o \
+ intel_bus_common.o
obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o
-soundwire-intel-init-objs := intel_init.o
-obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel-init.o
+#Qualcomm driver
+soundwire-qcom-y := qcom.o
+obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o
diff --git a/drivers/soundwire/amd_init.c b/drivers/soundwire/amd_init.c
new file mode 100644
index 000000000000..643e94524fe6
--- /dev/null
+++ b/drivers/soundwire/amd_init.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * SoundWire AMD Manager Initialize routines
+ *
+ * Initializes and creates SDW devices based on ACPI and Hardware values
+ *
+ * Copyright 2024 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "amd_init.h"
+
+#define ACP_PAD_PULLDOWN_CTRL 0x0001448
+#define ACP_SW_PAD_KEEPER_EN 0x0001454
+#define AMD_SDW0_PAD_CTRL_MASK 0x60
+#define AMD_SDW1_PAD_CTRL_MASK 5
+#define AMD_SDW_PAD_CTRL_MASK (AMD_SDW0_PAD_CTRL_MASK | AMD_SDW1_PAD_CTRL_MASK)
+#define AMD_SDW0_PAD_EN 1
+#define AMD_SDW1_PAD_EN 0x10
+#define AMD_SDW_PAD_EN (AMD_SDW0_PAD_EN | AMD_SDW1_PAD_EN)
+
+static int amd_enable_sdw_pads(void __iomem *mmio, u32 link_mask, struct device *dev)
+{
+ u32 pad_keeper_en, pad_pulldown_ctrl_mask;
+
+ switch (link_mask) {
+ case 1:
+ pad_keeper_en = AMD_SDW0_PAD_EN;
+ pad_pulldown_ctrl_mask = AMD_SDW0_PAD_CTRL_MASK;
+ break;
+ case 2:
+ pad_keeper_en = AMD_SDW1_PAD_EN;
+ pad_pulldown_ctrl_mask = AMD_SDW1_PAD_CTRL_MASK;
+ break;
+ case 3:
+ pad_keeper_en = AMD_SDW_PAD_EN;
+ pad_pulldown_ctrl_mask = AMD_SDW_PAD_CTRL_MASK;
+ break;
+ default:
+ dev_err(dev, "No SDW Links are enabled\n");
+ return -ENODEV;
+ }
+
+ amd_updatel(mmio, ACP_SW_PAD_KEEPER_EN, pad_keeper_en, pad_keeper_en);
+ amd_updatel(mmio, ACP_PAD_PULLDOWN_CTRL, pad_pulldown_ctrl_mask, 0);
+
+ return 0;
+}
+
+static int sdw_amd_cleanup(struct sdw_amd_ctx *ctx)
+{
+ int i;
+
+ for (i = 0; i < ctx->count; i++) {
+ if (!(ctx->link_mask & BIT(i)))
+ continue;
+ platform_device_unregister(ctx->pdev[i]);
+ }
+
+ return 0;
+}
+
+static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res)
+{
+ struct sdw_amd_ctx *ctx;
+ struct acpi_device *adev;
+ struct acp_sdw_pdata sdw_pdata[2];
+ struct platform_device_info pdevinfo[2];
+ u32 link_mask;
+ int count, index;
+ int ret;
+
+ 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);
+ ret = amd_enable_sdw_pads(res->mmio_base, res->link_mask, res->parent);
+ if (ret)
+ return NULL;
+
+ /*
+ * 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;
+ ctx->link_mask = res->link_mask;
+ struct resource *sdw_res __free(kfree) = kzalloc(sizeof(*sdw_res),
+ GFP_KERNEL);
+ if (!sdw_res) {
+ kfree(ctx);
+ return NULL;
+ }
+ sdw_res->flags = IORESOURCE_MEM;
+ sdw_res->start = res->addr;
+ sdw_res->end = res->addr + res->reg_range;
+ memset(&pdevinfo, 0, sizeof(pdevinfo));
+ link_mask = ctx->link_mask;
+ for (index = 0; index < count; index++) {
+ if (!(link_mask & BIT(index)))
+ continue;
+
+ sdw_pdata[index].instance = index;
+ sdw_pdata[index].acp_sdw_lock = res->acp_lock;
+ sdw_pdata[index].acp_rev = res->acp_rev;
+ pdevinfo[index].name = "amd_sdw_manager";
+ pdevinfo[index].id = index;
+ pdevinfo[index].parent = res->parent;
+ pdevinfo[index].num_res = 1;
+ pdevinfo[index].res = sdw_res;
+ pdevinfo[index].data = &sdw_pdata[index];
+ pdevinfo[index].size_data = sizeof(struct acp_sdw_pdata);
+ pdevinfo[index].fwnode = acpi_fwnode_handle(adev);
+ ctx->pdev[index] = platform_device_register_full(&pdevinfo[index]);
+ if (IS_ERR(ctx->pdev[index]))
+ goto err;
+ }
+ return ctx;
+err:
+ while (index--) {
+ if (!(link_mask & BIT(index)))
+ continue;
+
+ platform_device_unregister(ctx->pdev[index]);
+ }
+
+ kfree(ctx);
+ return NULL;
+}
+
+static int sdw_amd_startup(struct sdw_amd_ctx *ctx)
+{
+ struct amd_sdw_manager *amd_manager;
+ int i, ret;
+
+ /* Startup SDW Manager devices */
+ for (i = 0; i < ctx->count; i++) {
+ if (!(ctx->link_mask & BIT(i)))
+ continue;
+ amd_manager = dev_get_drvdata(&ctx->pdev[i]->dev);
+ ret = amd_sdw_manager_start(amd_manager);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int sdw_amd_probe(struct sdw_amd_res *res, struct sdw_amd_ctx **sdw_ctx)
+{
+ *sdw_ctx = sdw_amd_probe_controller(res);
+ if (!*sdw_ctx)
+ return -ENODEV;
+
+ return sdw_amd_startup(*sdw_ctx);
+}
+EXPORT_SYMBOL_NS(sdw_amd_probe, "SOUNDWIRE_AMD_INIT");
+
+void sdw_amd_exit(struct sdw_amd_ctx *ctx)
+{
+ sdw_amd_cleanup(ctx);
+ kfree(ctx->peripherals);
+ kfree(ctx);
+}
+EXPORT_SYMBOL_NS(sdw_amd_exit, "SOUNDWIRE_AMD_INIT");
+
+int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx)
+{
+ struct amd_sdw_manager *amd_manager;
+ struct sdw_bus *bus;
+ struct sdw_slave *slave;
+ struct list_head *node;
+ int index;
+ int i = 0;
+ int num_slaves = 0;
+
+ for (index = 0; index < ctx->count; index++) {
+ if (!(ctx->link_mask & BIT(index)))
+ continue;
+ amd_manager = dev_get_drvdata(&ctx->pdev[index]->dev);
+ if (!amd_manager)
+ return -ENODEV;
+ bus = &amd_manager->bus;
+ /* Calculate number of slaves */
+ list_for_each(node, &bus->slaves)
+ num_slaves++;
+ }
+
+ ctx->peripherals = kmalloc(struct_size(ctx->peripherals, array, num_slaves),
+ GFP_KERNEL);
+ if (!ctx->peripherals)
+ return -ENOMEM;
+ ctx->peripherals->num_peripherals = num_slaves;
+ for (index = 0; index < ctx->count; index++) {
+ if (!(ctx->link_mask & BIT(index)))
+ continue;
+ amd_manager = dev_get_drvdata(&ctx->pdev[index]->dev);
+ if (amd_manager) {
+ bus = &amd_manager->bus;
+ list_for_each_entry(slave, &bus->slaves, node) {
+ ctx->peripherals->array[i] = slave;
+ i++;
+ }
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS(sdw_amd_get_slave_info, "SOUNDWIRE_AMD_INIT");
+
+MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
+MODULE_DESCRIPTION("AMD SoundWire Init Library");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/soundwire/amd_init.h b/drivers/soundwire/amd_init.h
new file mode 100644
index 000000000000..5e7b43836a37
--- /dev/null
+++ b/drivers/soundwire/amd_init.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved.
+ */
+
+#ifndef __AMD_INIT_H
+#define __AMD_INIT_H
+
+#include <linux/soundwire/sdw_amd.h>
+
+int amd_sdw_manager_start(struct amd_sdw_manager *amd_manager);
+
+static inline void amd_updatel(void __iomem *mmio, int offset, u32 mask, u32 val)
+{
+ u32 tmp;
+
+ tmp = readl(mmio + offset);
+ tmp = (tmp & ~mask) | val;
+ writel(tmp, mmio + offset);
+}
+#endif
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c
new file mode 100644
index 000000000000..5fd311ee4107
--- /dev/null
+++ b/drivers/soundwire/amd_manager.c
@@ -0,0 +1,1385 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * SoundWire AMD Manager driver
+ *
+ * Copyright 2023-24 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/completion.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/pm_runtime.h>
+#include <linux/wait.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "bus.h"
+#include "amd_init.h"
+#include "amd_manager.h"
+
+#define DRV_NAME "amd_sdw_manager"
+
+#define to_amd_sdw(b) container_of(b, struct amd_sdw_manager, bus)
+
+static int amd_init_sdw_manager(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+ int ret;
+
+ writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_EN_STATUS, val, val, ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* SoundWire manager bus reset */
+ writel(AMD_SDW_BUS_RESET_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_BUS_RESET_CTRL, val,
+ (val & AMD_SDW_BUS_RESET_DONE), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret)
+ return ret;
+
+ writel(AMD_SDW_BUS_RESET_CLEAR_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_BUS_RESET_CTRL, val, !val,
+ ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "Failed to reset SoundWire manager instance%d\n",
+ amd_manager->instance);
+ return ret;
+ }
+
+ writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
+ return readl_poll_timeout(amd_manager->mmio + ACP_SW_EN_STATUS, val, !val, ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+}
+
+static int amd_enable_sdw_manager(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+
+ writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
+ return readl_poll_timeout(amd_manager->mmio + ACP_SW_EN_STATUS, val, val, ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+}
+
+static int amd_disable_sdw_manager(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+
+ writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
+ /*
+ * After invoking manager disable sequence, check whether
+ * manager has executed clock stop sequence. In this case,
+ * manager should ignore checking enable status register.
+ */
+ val = readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
+ if (val)
+ return 0;
+ return readl_poll_timeout(amd_manager->mmio + ACP_SW_EN_STATUS, val, !val, ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+}
+
+static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+
+ mutex_lock(amd_manager->acp_sdw_lock);
+ val = sdw_manager_reg_mask_array[amd_manager->instance];
+ amd_updatel(amd_manager->acp_mmio, ACP_EXTERNAL_INTR_CNTL(amd_manager->instance), val, val);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+
+ writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
+ ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
+ writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio +
+ ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+
+static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager)
+{
+ u32 irq_mask;
+
+ mutex_lock(amd_manager->acp_sdw_lock);
+ irq_mask = sdw_manager_reg_mask_array[amd_manager->instance];
+ amd_updatel(amd_manager->acp_mmio, ACP_EXTERNAL_INTR_CNTL(amd_manager->instance),
+ irq_mask, 0);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+
+ writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
+ writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ writel(0x00, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+
+static int amd_deinit_sdw_manager(struct amd_sdw_manager *amd_manager)
+{
+ amd_disable_sdw_interrupts(amd_manager);
+ return amd_disable_sdw_manager(amd_manager);
+}
+
+static void amd_sdw_set_frameshape(struct amd_sdw_manager *amd_manager)
+{
+ u32 frame_size;
+
+ frame_size = (amd_manager->rows_index << 3) | amd_manager->cols_index;
+ writel(frame_size, amd_manager->mmio + ACP_SW_FRAMESIZE);
+}
+
+static void amd_sdw_wake_enable(struct amd_sdw_manager *amd_manager, bool enable)
+{
+ u32 wake_ctrl;
+
+ wake_ctrl = readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ if (enable)
+ wake_ctrl |= AMD_SDW_WAKE_INTR_MASK;
+ else
+ wake_ctrl &= ~AMD_SDW_WAKE_INTR_MASK;
+
+ writel(wake_ctrl, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+}
+
+static int amd_sdw_set_device_state(struct amd_sdw_manager *amd_manager, u32 target_device_state)
+{
+ u32 sdw_dev_state;
+
+ sdw_dev_state = readl(amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE);
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ u32p_replace_bits(&sdw_dev_state, target_device_state,
+ AMD_SDW0_DEVICE_STATE_MASK);
+ break;
+ case ACP_SDW1:
+ u32p_replace_bits(&sdw_dev_state, target_device_state,
+ AMD_SDW1_DEVICE_STATE_MASK);
+ break;
+ default:
+ return -EINVAL;
+ }
+ writel(sdw_dev_state, amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE);
+ sdw_dev_state = readl(amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE);
+ dev_dbg(amd_manager->dev, "AMD_SDW_DEVICE_STATE:0x%x\n", sdw_dev_state);
+ return 0;
+}
+
+static int amd_sdw_host_wake_enable(struct amd_sdw_manager *amd_manager, bool enable)
+{
+ u32 intr_cntl1;
+ u32 sdw_host_wake_irq_mask;
+
+ if (!amd_manager->wake_en_mask)
+ return 0;
+
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ sdw_host_wake_irq_mask = AMD_SDW0_HOST_WAKE_INTR_MASK;
+ break;
+ case ACP_SDW1:
+ sdw_host_wake_irq_mask = AMD_SDW1_HOST_WAKE_INTR_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ intr_cntl1 = readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(ACP_SDW1));
+ if (enable)
+ intr_cntl1 |= sdw_host_wake_irq_mask;
+ else
+ intr_cntl1 &= ~sdw_host_wake_irq_mask;
+ writel(intr_cntl1, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(ACP_SDW1));
+ return 0;
+}
+
+static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, struct sdw_msg *msg,
+ int cmd_offset)
+{
+ u32 upper_data;
+ u32 lower_data = 0;
+ u16 addr;
+ u8 upper_addr, lower_addr;
+ u8 data = 0;
+
+ addr = msg->addr + cmd_offset;
+ upper_addr = (addr & 0xFF00) >> 8;
+ lower_addr = addr & 0xFF;
+
+ if (msg->flags == SDW_MSG_FLAG_WRITE)
+ data = msg->buf[cmd_offset];
+
+ upper_data = FIELD_PREP(AMD_SDW_MCP_CMD_DEV_ADDR, msg->dev_num);
+ upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_COMMAND, msg->flags + 2);
+ upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_HIGH, upper_addr);
+ lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_LOW, lower_addr);
+ lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_DATA, data);
+
+ *upper_word = upper_data;
+ *lower_word = lower_data;
+}
+
+static u64 amd_sdw_send_cmd_get_resp(struct amd_sdw_manager *amd_manager, u32 lower_data,
+ u32 upper_data)
+{
+ u64 resp;
+ u32 lower_resp, upper_resp;
+ u32 sts;
+ int ret;
+
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_IMM_CMD_STS, sts,
+ !(sts & AMD_SDW_IMM_CMD_BUSY), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "SDW%x previous cmd status clear failed\n",
+ amd_manager->instance);
+ return ret;
+ }
+
+ if (sts & AMD_SDW_IMM_RES_VALID) {
+ dev_err(amd_manager->dev, "SDW%x manager is in bad state\n", amd_manager->instance);
+ writel(AMD_SDW_IMM_RES_VALID, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
+ }
+ writel(upper_data, amd_manager->mmio + ACP_SW_IMM_CMD_UPPER_WORD);
+ writel(lower_data, amd_manager->mmio + ACP_SW_IMM_CMD_LOWER_QWORD);
+
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_IMM_CMD_STS, sts,
+ (sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "SDW%x cmd response timeout occurred\n",
+ amd_manager->instance);
+ return ret;
+ }
+ upper_resp = readl(amd_manager->mmio + ACP_SW_IMM_RESP_UPPER_WORD);
+ lower_resp = readl(amd_manager->mmio + ACP_SW_IMM_RESP_LOWER_QWORD);
+
+ writel(AMD_SDW_IMM_RES_VALID, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_IMM_CMD_STS, sts,
+ !(sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "SDW%x cmd status retry failed\n",
+ amd_manager->instance);
+ return ret;
+ }
+ resp = upper_resp;
+ resp = (resp << 32) | lower_resp;
+ return resp;
+}
+
+static enum sdw_command_response
+amd_program_scp_addr(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg)
+{
+ struct sdw_msg scp_msg = {0};
+ u64 response_buf[2] = {0};
+ u32 upper_data = 0, lower_data = 0;
+ int index;
+
+ scp_msg.dev_num = msg->dev_num;
+ scp_msg.addr = SDW_SCP_ADDRPAGE1;
+ scp_msg.buf = &msg->addr_page1;
+ scp_msg.flags = SDW_MSG_FLAG_WRITE;
+ amd_sdw_ctl_word_prep(&lower_data, &upper_data, &scp_msg, 0);
+ response_buf[0] = amd_sdw_send_cmd_get_resp(amd_manager, lower_data, upper_data);
+ scp_msg.addr = SDW_SCP_ADDRPAGE2;
+ scp_msg.buf = &msg->addr_page2;
+ amd_sdw_ctl_word_prep(&lower_data, &upper_data, &scp_msg, 0);
+ response_buf[1] = amd_sdw_send_cmd_get_resp(amd_manager, lower_data, upper_data);
+
+ for (index = 0; index < 2; index++) {
+ if (response_buf[index] == -ETIMEDOUT) {
+ dev_err_ratelimited(amd_manager->dev,
+ "SCP_addrpage command timeout for Slave %d\n",
+ msg->dev_num);
+ return SDW_CMD_TIMEOUT;
+ } else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
+ if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
+ dev_err_ratelimited(amd_manager->dev,
+ "SCP_addrpage NACKed for Slave %d\n",
+ msg->dev_num);
+ return SDW_CMD_FAIL;
+ }
+ dev_dbg_ratelimited(amd_manager->dev, "SCP_addrpage ignored for Slave %d\n",
+ msg->dev_num);
+ return SDW_CMD_IGNORED;
+ }
+ }
+ return SDW_CMD_OK;
+}
+
+static int amd_prep_msg(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg)
+{
+ int ret;
+
+ if (msg->page) {
+ ret = amd_program_scp_addr(amd_manager, msg);
+ if (ret) {
+ msg->len = 0;
+ return ret;
+ }
+ }
+ switch (msg->flags) {
+ case SDW_MSG_FLAG_READ:
+ case SDW_MSG_FLAG_WRITE:
+ break;
+ default:
+ dev_err(amd_manager->dev, "Invalid msg cmd: %d\n", msg->flags);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum sdw_command_response amd_sdw_fill_msg_resp(struct amd_sdw_manager *amd_manager,
+ struct sdw_msg *msg, u64 response,
+ int offset)
+{
+ if (response & AMD_SDW_MCP_RESP_ACK) {
+ if (msg->flags == SDW_MSG_FLAG_READ)
+ msg->buf[offset] = FIELD_GET(AMD_SDW_MCP_RESP_RDATA, response);
+ } else {
+ if (response == -ETIMEDOUT) {
+ dev_err_ratelimited(amd_manager->dev, "command timeout for Slave %d\n",
+ msg->dev_num);
+ return SDW_CMD_TIMEOUT;
+ } else if (response & AMD_SDW_MCP_RESP_NACK) {
+ dev_err_ratelimited(amd_manager->dev,
+ "command response NACK received for Slave %d\n",
+ msg->dev_num);
+ return SDW_CMD_FAIL;
+ }
+ dev_dbg_ratelimited(amd_manager->dev, "command is ignored for Slave %d\n",
+ msg->dev_num);
+ return SDW_CMD_IGNORED;
+ }
+ return SDW_CMD_OK;
+}
+
+static unsigned int _amd_sdw_xfer_msg(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg,
+ int cmd_offset)
+{
+ u64 response;
+ u32 upper_data = 0, lower_data = 0;
+
+ amd_sdw_ctl_word_prep(&lower_data, &upper_data, msg, cmd_offset);
+ response = amd_sdw_send_cmd_get_resp(amd_manager, lower_data, upper_data);
+ return amd_sdw_fill_msg_resp(amd_manager, msg, response, cmd_offset);
+}
+
+static enum sdw_command_response amd_sdw_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ int ret, i;
+
+ ret = amd_prep_msg(amd_manager, msg);
+ if (ret)
+ return SDW_CMD_FAIL_OTHER;
+ for (i = 0; i < msg->len; i++) {
+ ret = _amd_sdw_xfer_msg(amd_manager, msg, i);
+ if (ret)
+ return ret;
+ }
+ return SDW_CMD_OK;
+}
+
+static void amd_sdw_fill_slave_status(struct amd_sdw_manager *amd_manager, u16 index, u32 status)
+{
+ switch (status) {
+ case SDW_SLAVE_ATTACHED:
+ case SDW_SLAVE_UNATTACHED:
+ case SDW_SLAVE_ALERT:
+ amd_manager->status[index] = status;
+ break;
+ default:
+ amd_manager->status[index] = SDW_SLAVE_RESERVED;
+ break;
+ }
+}
+
+static void amd_sdw_process_ping_status(u64 response, struct amd_sdw_manager *amd_manager)
+{
+ u64 slave_stat;
+ u32 val;
+ u16 dev_index;
+
+ /* slave status response */
+ slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
+ slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
+ dev_dbg(amd_manager->dev, "slave_stat:0x%llx\n", slave_stat);
+ for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) {
+ val = (slave_stat >> (dev_index * 2)) & AMD_SDW_MCP_SLAVE_STATUS_MASK;
+ dev_dbg(amd_manager->dev, "val:0x%x\n", val);
+ amd_sdw_fill_slave_status(amd_manager, dev_index, val);
+ }
+}
+
+static void amd_sdw_read_and_process_ping_status(struct amd_sdw_manager *amd_manager)
+{
+ u64 response;
+
+ mutex_lock(&amd_manager->bus.msg_lock);
+ response = amd_sdw_send_cmd_get_resp(amd_manager, 0, 0);
+ mutex_unlock(&amd_manager->bus.msg_lock);
+ amd_sdw_process_ping_status(response, amd_manager);
+}
+
+static u32 amd_sdw_read_ping_status(struct sdw_bus *bus)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u64 response;
+ u32 slave_stat;
+
+ response = amd_sdw_send_cmd_get_resp(amd_manager, 0, 0);
+ /* slave status from ping response */
+ slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
+ slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
+ dev_dbg(amd_manager->dev, "slave_stat:0x%x\n", slave_stat);
+ return slave_stat;
+}
+
+static int amd_sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
+{
+ struct sdw_transport_data t_data = {0};
+ struct sdw_master_runtime *m_rt;
+ struct sdw_port_runtime *p_rt;
+ struct sdw_bus_params *b_params = &bus->params;
+ int port_bo, hstart, hstop, sample_int;
+ unsigned int rate, bps;
+
+ port_bo = 0;
+ hstart = 1;
+ hstop = bus->params.col - 1;
+ t_data.hstop = hstop;
+ t_data.hstart = hstart;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ rate = m_rt->stream->params.rate;
+ bps = m_rt->stream->params.bps;
+ sample_int = (bus->params.curr_dr_freq / rate);
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ port_bo = (p_rt->num * 64) + 1;
+ dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n",
+ p_rt->num, hstart, hstop, port_bo);
+ 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, p_rt->lane);
+
+ sdw_fill_port_params(&p_rt->port_params,
+ p_rt->num, bps,
+ SDW_PORT_FLOW_MODE_ISOCH,
+ b_params->m_data_mode);
+ t_data.hstart = hstart;
+ t_data.hstop = hstop;
+ t_data.block_offset = port_bo;
+ t_data.sub_block_offset = 0;
+ }
+ sdw_compute_slave_ports(m_rt, &t_data);
+ }
+ return 0;
+}
+
+static int amd_sdw_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params,
+ unsigned int bank)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u32 frame_fmt_reg, dpn_frame_fmt;
+
+ dev_dbg(amd_manager->dev, "p_params->num:0x%x\n", p_params->num);
+ switch (amd_manager->acp_rev) {
+ case ACP63_PCI_REV_ID:
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ frame_fmt_reg = acp63_sdw0_dp_reg[p_params->num].frame_fmt_reg;
+ break;
+ case ACP_SDW1:
+ frame_fmt_reg = acp63_sdw1_dp_reg[p_params->num].frame_fmt_reg;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case ACP70_PCI_REV_ID:
+ case ACP71_PCI_REV_ID:
+ case ACP72_PCI_REV_ID:
+ frame_fmt_reg = acp70_sdw_dp_reg[p_params->num].frame_fmt_reg;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dpn_frame_fmt = readl(amd_manager->mmio + frame_fmt_reg);
+ u32p_replace_bits(&dpn_frame_fmt, p_params->flow_mode, AMD_DPN_FRAME_FMT_PFM);
+ u32p_replace_bits(&dpn_frame_fmt, p_params->data_mode, AMD_DPN_FRAME_FMT_PDM);
+ u32p_replace_bits(&dpn_frame_fmt, p_params->bps - 1, AMD_DPN_FRAME_FMT_WORD_LEN);
+ writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg);
+ return 0;
+}
+
+static int amd_sdw_transport_params(struct sdw_bus *bus,
+ struct sdw_transport_params *params,
+ enum sdw_reg_bank bank)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u32 dpn_frame_fmt;
+ u32 dpn_sampleinterval;
+ u32 dpn_hctrl;
+ u32 dpn_offsetctrl;
+ u32 dpn_lanectrl;
+ u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg;
+ u32 offset_reg, lane_ctrl_ch_en_reg;
+
+ switch (amd_manager->acp_rev) {
+ case ACP63_PCI_REV_ID:
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ frame_fmt_reg = acp63_sdw0_dp_reg[params->port_num].frame_fmt_reg;
+ sample_int_reg = acp63_sdw0_dp_reg[params->port_num].sample_int_reg;
+ hctrl_dp0_reg = acp63_sdw0_dp_reg[params->port_num].hctrl_dp0_reg;
+ offset_reg = acp63_sdw0_dp_reg[params->port_num].offset_reg;
+ lane_ctrl_ch_en_reg =
+ acp63_sdw0_dp_reg[params->port_num].lane_ctrl_ch_en_reg;
+ break;
+ case ACP_SDW1:
+ frame_fmt_reg = acp63_sdw1_dp_reg[params->port_num].frame_fmt_reg;
+ sample_int_reg = acp63_sdw1_dp_reg[params->port_num].sample_int_reg;
+ hctrl_dp0_reg = acp63_sdw1_dp_reg[params->port_num].hctrl_dp0_reg;
+ offset_reg = acp63_sdw1_dp_reg[params->port_num].offset_reg;
+ lane_ctrl_ch_en_reg =
+ acp63_sdw1_dp_reg[params->port_num].lane_ctrl_ch_en_reg;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case ACP70_PCI_REV_ID:
+ case ACP71_PCI_REV_ID:
+ case ACP72_PCI_REV_ID:
+ frame_fmt_reg = acp70_sdw_dp_reg[params->port_num].frame_fmt_reg;
+ sample_int_reg = acp70_sdw_dp_reg[params->port_num].sample_int_reg;
+ hctrl_dp0_reg = acp70_sdw_dp_reg[params->port_num].hctrl_dp0_reg;
+ offset_reg = acp70_sdw_dp_reg[params->port_num].offset_reg;
+ lane_ctrl_ch_en_reg = acp70_sdw_dp_reg[params->port_num].lane_ctrl_ch_en_reg;
+ break;
+ default:
+ return -EINVAL;
+ }
+ writel(AMD_SDW_SSP_COUNTER_VAL, amd_manager->mmio + ACP_SW_SSP_COUNTER);
+
+ dpn_frame_fmt = readl(amd_manager->mmio + frame_fmt_reg);
+ u32p_replace_bits(&dpn_frame_fmt, params->blk_pkg_mode, AMD_DPN_FRAME_FMT_BLK_PKG_MODE);
+ u32p_replace_bits(&dpn_frame_fmt, params->blk_grp_ctrl, AMD_DPN_FRAME_FMT_BLK_GRP_CTRL);
+ u32p_replace_bits(&dpn_frame_fmt, SDW_STREAM_PCM, AMD_DPN_FRAME_FMT_PCM_OR_PDM);
+ writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg);
+
+ dpn_sampleinterval = params->sample_interval - 1;
+ writel(dpn_sampleinterval, amd_manager->mmio + sample_int_reg);
+
+ dpn_hctrl = FIELD_PREP(AMD_DPN_HCTRL_HSTOP, params->hstop);
+ dpn_hctrl |= FIELD_PREP(AMD_DPN_HCTRL_HSTART, params->hstart);
+ writel(dpn_hctrl, amd_manager->mmio + hctrl_dp0_reg);
+
+ dpn_offsetctrl = FIELD_PREP(AMD_DPN_OFFSET_CTRL_1, params->offset1);
+ dpn_offsetctrl |= FIELD_PREP(AMD_DPN_OFFSET_CTRL_2, params->offset2);
+ writel(dpn_offsetctrl, amd_manager->mmio + offset_reg);
+
+ /*
+ * lane_ctrl_ch_en_reg will be used to program lane_ctrl and ch_mask
+ * parameters.
+ */
+ dpn_lanectrl = readl(amd_manager->mmio + lane_ctrl_ch_en_reg);
+ u32p_replace_bits(&dpn_lanectrl, params->lane_ctrl, AMD_DPN_CH_EN_LCTRL);
+ writel(dpn_lanectrl, amd_manager->mmio + lane_ctrl_ch_en_reg);
+ return 0;
+}
+
+static int amd_sdw_port_enable(struct sdw_bus *bus,
+ struct sdw_enable_ch *enable_ch,
+ unsigned int bank)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u32 dpn_ch_enable;
+ u32 lane_ctrl_ch_en_reg;
+
+ switch (amd_manager->acp_rev) {
+ case ACP63_PCI_REV_ID:
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ lane_ctrl_ch_en_reg =
+ acp63_sdw0_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg;
+ break;
+ case ACP_SDW1:
+ lane_ctrl_ch_en_reg =
+ acp63_sdw1_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case ACP70_PCI_REV_ID:
+ case ACP71_PCI_REV_ID:
+ case ACP72_PCI_REV_ID:
+ lane_ctrl_ch_en_reg = acp70_sdw_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * lane_ctrl_ch_en_reg will be used to program lane_ctrl and ch_mask
+ * parameters.
+ */
+ dpn_ch_enable = readl(amd_manager->mmio + lane_ctrl_ch_en_reg);
+ u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK);
+ if (enable_ch->enable)
+ writel(dpn_ch_enable, amd_manager->mmio + lane_ctrl_ch_en_reg);
+ else
+ writel(0, amd_manager->mmio + lane_ctrl_ch_en_reg);
+ return 0;
+}
+
+static int sdw_master_read_amd_prop(struct sdw_bus *bus)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ struct fwnode_handle *link;
+ struct sdw_master_prop *prop;
+ u32 quirk_mask = 0;
+ u32 wake_en_mask = 0;
+ u32 power_mode_mask = 0;
+ char name[32];
+
+ prop = &bus->prop;
+ /* Find manager 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, "Manager node %s not found\n", name);
+ return -EIO;
+ }
+ fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask);
+ if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE))
+ prop->hw_disabled = true;
+ prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
+ SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
+
+ fwnode_property_read_u32(link, "amd-sdw-wakeup-enable", &wake_en_mask);
+ amd_manager->wake_en_mask = wake_en_mask;
+ fwnode_property_read_u32(link, "amd-sdw-power-mode", &power_mode_mask);
+ amd_manager->power_mode_mask = power_mode_mask;
+
+ fwnode_handle_put(link);
+
+ return 0;
+}
+
+static int amd_prop_read(struct sdw_bus *bus)
+{
+ sdw_master_read_prop(bus);
+ sdw_master_read_amd_prop(bus);
+ return 0;
+}
+
+static const struct sdw_master_port_ops amd_sdw_port_ops = {
+ .dpn_set_port_params = amd_sdw_port_params,
+ .dpn_set_port_transport_params = amd_sdw_transport_params,
+ .dpn_port_enable_ch = amd_sdw_port_enable,
+};
+
+static const struct sdw_master_ops amd_sdw_ops = {
+ .read_prop = amd_prop_read,
+ .xfer_msg = amd_sdw_xfer_msg,
+ .read_ping_status = amd_sdw_read_ping_status,
+};
+
+static int amd_sdw_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
+ struct sdw_amd_dai_runtime *dai_runtime;
+ struct sdw_stream_config sconfig;
+ int ch, dir;
+ int ret;
+
+ dai_runtime = amd_manager->dai_runtime_array[dai->id];
+ 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;
+ dev_dbg(amd_manager->dev, "dir:%d dai->id:0x%x\n", dir, dai->id);
+
+ 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 */
+ struct sdw_port_config *pconfig __free(kfree) = kzalloc(sizeof(*pconfig),
+ GFP_KERNEL);
+ if (!pconfig)
+ return -ENOMEM;
+
+ pconfig->num = dai->id;
+ pconfig->ch_mask = (1 << ch) - 1;
+ ret = sdw_stream_add_master(&amd_manager->bus, &sconfig,
+ pconfig, 1, dai_runtime->stream);
+ if (ret)
+ dev_err(amd_manager->dev, "add manager to stream failed:%d\n", ret);
+
+ return ret;
+}
+
+static int amd_sdw_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
+ struct sdw_amd_dai_runtime *dai_runtime;
+ int ret;
+
+ dai_runtime = amd_manager->dai_runtime_array[dai->id];
+ if (!dai_runtime)
+ return -EIO;
+
+ ret = sdw_stream_remove_master(&amd_manager->bus, dai_runtime->stream);
+ if (ret < 0)
+ dev_err(dai->dev, "remove manager from stream %s failed: %d\n",
+ dai_runtime->stream->name, ret);
+ return ret;
+}
+
+static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction)
+{
+ struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
+ struct sdw_amd_dai_runtime *dai_runtime;
+
+ dai_runtime = amd_manager->dai_runtime_array[dai->id];
+ if (stream) {
+ /* first paranoia check */
+ 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 = &amd_manager->bus;
+ dai_runtime->stream = stream;
+ amd_manager->dai_runtime_array[dai->id] = dai_runtime;
+ } else {
+ /* second paranoia check */
+ if (!dai_runtime) {
+ dev_err(dai->dev, "dai_runtime not allocated for dai %s\n", dai->name);
+ return -EINVAL;
+ }
+
+ /* for NULL stream we release allocated dai_runtime */
+ kfree(dai_runtime);
+ amd_manager->dai_runtime_array[dai->id] = NULL;
+ }
+ return 0;
+}
+
+static int amd_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction)
+{
+ return amd_set_sdw_stream(dai, stream, direction);
+}
+
+static void *amd_get_sdw_stream(struct snd_soc_dai *dai, int direction)
+{
+ struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
+ struct sdw_amd_dai_runtime *dai_runtime;
+
+ dai_runtime = amd_manager->dai_runtime_array[dai->id];
+ if (!dai_runtime)
+ return ERR_PTR(-EINVAL);
+
+ return dai_runtime->stream;
+}
+
+static const struct snd_soc_dai_ops amd_sdw_dai_ops = {
+ .hw_params = amd_sdw_hw_params,
+ .hw_free = amd_sdw_hw_free,
+ .set_stream = amd_pcm_set_sdw_stream,
+ .get_stream = amd_get_sdw_stream,
+};
+
+static const struct snd_soc_component_driver amd_sdw_dai_component = {
+ .name = "soundwire",
+};
+
+static int amd_sdw_register_dais(struct amd_sdw_manager *amd_manager)
+{
+ struct sdw_amd_dai_runtime **dai_runtime_array;
+ struct snd_soc_dai_driver *dais;
+ struct snd_soc_pcm_stream *stream;
+ struct device *dev;
+ int i, num_dais;
+
+ dev = amd_manager->dev;
+ num_dais = amd_manager->num_dout_ports + amd_manager->num_din_ports;
+ dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL);
+ if (!dais)
+ return -ENOMEM;
+
+ dai_runtime_array = devm_kcalloc(dev, num_dais,
+ sizeof(struct sdw_amd_dai_runtime *),
+ GFP_KERNEL);
+ if (!dai_runtime_array)
+ return -ENOMEM;
+ amd_manager->dai_runtime_array = dai_runtime_array;
+ for (i = 0; i < num_dais; i++) {
+ dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", amd_manager->instance,
+ i);
+ if (!dais[i].name)
+ return -ENOMEM;
+ if (i < amd_manager->num_dout_ports)
+ stream = &dais[i].playback;
+ else
+ stream = &dais[i].capture;
+
+ stream->channels_min = 2;
+ stream->channels_max = 2;
+ stream->rates = SNDRV_PCM_RATE_48000;
+ stream->formats = SNDRV_PCM_FMTBIT_S16_LE;
+
+ dais[i].ops = &amd_sdw_dai_ops;
+ dais[i].id = i;
+ }
+
+ return devm_snd_soc_register_component(dev, &amd_sdw_dai_component,
+ dais, num_dais);
+}
+
+static void amd_sdw_update_slave_status_work(struct work_struct *work)
+{
+ struct amd_sdw_manager *amd_manager =
+ container_of(work, struct amd_sdw_manager, amd_sdw_work);
+ int retry_count = 0;
+
+ if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
+ writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
+ writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ }
+
+update_status:
+ sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
+ /*
+ * During the peripheral enumeration sequence, the SoundWire manager interrupts
+ * are masked. Once the device number programming is done for all peripherals,
+ * interrupts will be unmasked. Read the peripheral device status from ping command
+ * and process the response. This sequence will ensure all peripheral devices enumerated
+ * and initialized properly.
+ */
+ if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
+ if (retry_count++ < SDW_MAX_DEVICES) {
+ writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
+ ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
+ writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio +
+ ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ amd_sdw_read_and_process_ping_status(amd_manager);
+ goto update_status;
+ } else {
+ dev_err_ratelimited(amd_manager->dev,
+ "Device0 detected after %d iterations\n",
+ retry_count);
+ }
+ }
+}
+
+static void amd_sdw_update_slave_status(u32 status_change_0to7, u32 status_change_8to11,
+ struct amd_sdw_manager *amd_manager)
+{
+ u64 slave_stat;
+ u32 val;
+ int dev_index;
+
+ if (status_change_0to7 == AMD_SDW_SLAVE_0_ATTACHED)
+ memset(amd_manager->status, 0, sizeof(amd_manager->status));
+ slave_stat = status_change_0to7;
+ slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STATUS_8TO_11, status_change_8to11) << 32;
+ dev_dbg(amd_manager->dev, "status_change_0to7:0x%x status_change_8to11:0x%x\n",
+ status_change_0to7, status_change_8to11);
+ if (slave_stat) {
+ for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) {
+ if (slave_stat & AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(dev_index)) {
+ val = (slave_stat >> AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(dev_index)) &
+ AMD_SDW_MCP_SLAVE_STATUS_MASK;
+ amd_sdw_fill_slave_status(amd_manager, dev_index, val);
+ }
+ }
+ }
+}
+
+static void amd_sdw_process_wake_event(struct amd_sdw_manager *amd_manager)
+{
+ dev_dbg(amd_manager->dev, "SoundWire Wake event reported\n");
+ pm_request_resume(amd_manager->dev);
+ writel(0x00, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance));
+ writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11);
+}
+
+static void amd_sdw_irq_thread(struct work_struct *work)
+{
+ struct amd_sdw_manager *amd_manager =
+ container_of(work, struct amd_sdw_manager, amd_sdw_irq_thread);
+ u32 status_change_8to11;
+ u32 status_change_0to7;
+
+ status_change_8to11 = readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11);
+ status_change_0to7 = readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7);
+ if (!status_change_0to7 && !status_change_8to11)
+ return;
+
+ dev_dbg(amd_manager->dev, "[SDW%d] SDW INT: 0to7=0x%x, 8to11=0x%x\n",
+ amd_manager->instance, status_change_0to7, status_change_8to11);
+ if (status_change_8to11 & AMD_SDW_WAKE_STAT_MASK)
+ return amd_sdw_process_wake_event(amd_manager);
+
+ if (status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) {
+ amd_sdw_read_and_process_ping_status(amd_manager);
+ } else {
+ /* Check for the updated status on peripheral device */
+ amd_sdw_update_slave_status(status_change_0to7, status_change_8to11, amd_manager);
+ }
+ if (status_change_8to11 || status_change_0to7)
+ schedule_work(&amd_manager->amd_sdw_work);
+ writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11);
+ writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7);
+}
+
+int amd_sdw_manager_start(struct amd_sdw_manager *amd_manager)
+{
+ struct sdw_master_prop *prop;
+ int ret;
+
+ prop = &amd_manager->bus.prop;
+ if (!prop->hw_disabled) {
+ ret = amd_init_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ amd_enable_sdw_interrupts(amd_manager);
+ ret = amd_enable_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ amd_sdw_set_frameshape(amd_manager);
+ }
+ /* Enable runtime PM */
+ pm_runtime_set_autosuspend_delay(amd_manager->dev, AMD_SDW_MASTER_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(amd_manager->dev);
+ pm_runtime_mark_last_busy(amd_manager->dev);
+ pm_runtime_set_active(amd_manager->dev);
+ pm_runtime_enable(amd_manager->dev);
+ return 0;
+}
+
+static int amd_sdw_manager_probe(struct platform_device *pdev)
+{
+ const struct acp_sdw_pdata *pdata = pdev->dev.platform_data;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct sdw_master_prop *prop;
+ struct sdw_bus_params *params;
+ struct amd_sdw_manager *amd_manager;
+ int ret;
+
+ amd_manager = devm_kzalloc(dev, sizeof(struct amd_sdw_manager), GFP_KERNEL);
+ if (!amd_manager)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOMEM;
+
+ amd_manager->acp_mmio = devm_ioremap(dev, res->start, resource_size(res));
+ if (!amd_manager->acp_mmio) {
+ dev_err(dev, "mmio not found\n");
+ return -ENOMEM;
+ }
+ amd_manager->instance = pdata->instance;
+ amd_manager->mmio = amd_manager->acp_mmio +
+ (amd_manager->instance * SDW_MANAGER_REG_OFFSET);
+ amd_manager->acp_sdw_lock = pdata->acp_sdw_lock;
+ amd_manager->acp_rev = pdata->acp_rev;
+ amd_manager->cols_index = sdw_find_col_index(AMD_SDW_DEFAULT_COLUMNS);
+ amd_manager->rows_index = sdw_find_row_index(AMD_SDW_DEFAULT_ROWS);
+ amd_manager->dev = dev;
+ amd_manager->bus.ops = &amd_sdw_ops;
+ amd_manager->bus.port_ops = &amd_sdw_port_ops;
+ amd_manager->bus.compute_params = &amd_sdw_compute_params;
+ amd_manager->bus.clk_stop_timeout = 200;
+ amd_manager->bus.link_id = amd_manager->instance;
+
+ /*
+ * Due to BIOS compatibility, the two links are exposed within
+ * the scope of a single controller. If this changes, the
+ * controller_id will have to be updated with drv_data
+ * information.
+ */
+ amd_manager->bus.controller_id = 0;
+ dev_dbg(dev, "acp_rev:0x%x\n", amd_manager->acp_rev);
+ switch (amd_manager->acp_rev) {
+ case ACP63_PCI_REV_ID:
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ amd_manager->num_dout_ports = AMD_ACP63_SDW0_MAX_TX_PORTS;
+ amd_manager->num_din_ports = AMD_ACP63_SDW0_MAX_RX_PORTS;
+ break;
+ case ACP_SDW1:
+ amd_manager->num_dout_ports = AMD_ACP63_SDW1_MAX_TX_PORTS;
+ amd_manager->num_din_ports = AMD_ACP63_SDW1_MAX_RX_PORTS;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case ACP70_PCI_REV_ID:
+ case ACP71_PCI_REV_ID:
+ case ACP72_PCI_REV_ID:
+ amd_manager->num_dout_ports = AMD_ACP70_SDW_MAX_TX_PORTS;
+ amd_manager->num_din_ports = AMD_ACP70_SDW_MAX_RX_PORTS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ params = &amd_manager->bus.params;
+
+ params->col = AMD_SDW_DEFAULT_COLUMNS;
+ params->row = AMD_SDW_DEFAULT_ROWS;
+ prop = &amd_manager->bus.prop;
+ prop->clk_freq = &amd_sdw_freq_tbl[0];
+ prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ;
+ prop->max_clk_freq = AMD_SDW_DEFAULT_CLK_FREQ;
+
+ ret = sdw_bus_master_add(&amd_manager->bus, dev, dev->fwnode);
+ if (ret) {
+ dev_err(dev, "Failed to register SoundWire manager(%d)\n", ret);
+ return ret;
+ }
+ ret = amd_sdw_register_dais(amd_manager);
+ if (ret) {
+ dev_err(dev, "CPU DAI registration failed\n");
+ sdw_bus_master_delete(&amd_manager->bus);
+ return ret;
+ }
+ dev_set_drvdata(dev, amd_manager);
+ INIT_WORK(&amd_manager->amd_sdw_irq_thread, amd_sdw_irq_thread);
+ INIT_WORK(&amd_manager->amd_sdw_work, amd_sdw_update_slave_status_work);
+ return 0;
+}
+
+static void amd_sdw_manager_remove(struct platform_device *pdev)
+{
+ struct amd_sdw_manager *amd_manager = dev_get_drvdata(&pdev->dev);
+ int ret;
+
+ pm_runtime_disable(&pdev->dev);
+ cancel_work_sync(&amd_manager->amd_sdw_work);
+ amd_disable_sdw_interrupts(amd_manager);
+ sdw_bus_master_delete(&amd_manager->bus);
+ ret = amd_disable_sdw_manager(amd_manager);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to disable device (%pe)\n", ERR_PTR(ret));
+}
+
+static int amd_sdw_clock_stop(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+ int ret;
+
+ ret = sdw_bus_prep_clk_stop(&amd_manager->bus);
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(amd_manager->dev, "prepare clock stop failed %d", ret);
+ return 0;
+ }
+ ret = sdw_bus_clk_stop(&amd_manager->bus);
+ if (ret < 0 && ret != -ENODATA) {
+ dev_err(amd_manager->dev, "bus clock stop failed %d", ret);
+ return 0;
+ }
+
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL, val,
+ (val & AMD_SDW_CLK_STOP_DONE), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "SDW%x clock stop failed\n", amd_manager->instance);
+ return 0;
+ }
+
+ amd_manager->clk_stopped = true;
+ if (amd_manager->wake_en_mask)
+ writel(0x01, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance));
+
+ dev_dbg(amd_manager->dev, "SDW%x clock stop successful\n", amd_manager->instance);
+ return 0;
+}
+
+static int amd_sdw_clock_stop_exit(struct amd_sdw_manager *amd_manager)
+{
+ int ret;
+ u32 val;
+
+ if (amd_manager->clk_stopped) {
+ val = readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
+ val |= AMD_SDW_CLK_RESUME_REQ;
+ writel(val, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL, val,
+ (val & AMD_SDW_CLK_RESUME_DONE), ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+ if (val & AMD_SDW_CLK_RESUME_DONE) {
+ writel(0, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
+ ret = sdw_bus_exit_clk_stop(&amd_manager->bus);
+ if (ret < 0)
+ dev_err(amd_manager->dev, "bus failed to exit clock stop %d\n",
+ ret);
+ amd_manager->clk_stopped = false;
+ }
+ }
+ if (amd_manager->clk_stopped) {
+ dev_err(amd_manager->dev, "SDW%x clock stop exit failed\n", amd_manager->instance);
+ return 0;
+ }
+ dev_dbg(amd_manager->dev, "SDW%x clock stop exit successful\n", amd_manager->instance);
+ return 0;
+}
+
+static int amd_resume_child_device(struct device *dev, void *data)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ int ret;
+
+ 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, "pm_request_resume failed: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int __maybe_unused amd_pm_prepare(struct device *dev)
+{
+ struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev);
+ struct sdw_bus *bus = &amd_manager->bus;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+ /*
+ * When multiple peripheral devices connected over the same link, if SoundWire manager
+ * device is not in runtime suspend state, observed that device alerts are missing
+ * without pm_prepare on AMD platforms in clockstop mode0.
+ */
+ if (amd_manager->power_mode_mask) {
+ ret = pm_runtime_resume(dev);
+ if (ret < 0) {
+ dev_err(bus->dev, "pm_runtime_resume failed: %d\n", ret);
+ return 0;
+ }
+ }
+ /* To force peripheral devices to system level suspend state, resume the devices
+ * from runtime suspend state first. Without that unable to dispatch the alert
+ * status to peripheral driver during system level resume as they are in runtime
+ * suspend state.
+ */
+ ret = device_for_each_child(bus->dev, NULL, amd_resume_child_device);
+ if (ret < 0)
+ dev_err(dev, "amd_resume_child_device failed: %d\n", ret);
+ return 0;
+}
+
+static int __maybe_unused amd_suspend(struct device *dev)
+{
+ struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev);
+ struct sdw_bus *bus = &amd_manager->bus;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
+ cancel_work_sync(&amd_manager->amd_sdw_work);
+ amd_sdw_wake_enable(amd_manager, false);
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_host_wake_enable(amd_manager, false);
+ if (ret)
+ return ret;
+ }
+ ret = amd_sdw_clock_stop(amd_manager);
+ if (ret)
+ return ret;
+ } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
+ cancel_work_sync(&amd_manager->amd_sdw_work);
+ amd_sdw_wake_enable(amd_manager, false);
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_host_wake_enable(amd_manager, false);
+ if (ret)
+ return ret;
+ }
+ /*
+ * As per hardware programming sequence on AMD platforms,
+ * clock stop should be invoked first before powering-off
+ */
+ ret = amd_sdw_clock_stop(amd_manager);
+ if (ret)
+ return ret;
+ ret = amd_deinit_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ }
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int __maybe_unused amd_suspend_runtime(struct device *dev)
+{
+ struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev);
+ struct sdw_bus *bus = &amd_manager->bus;
+ int ret;
+ u32 val;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(bus->dev, "SoundWire manager %d is disabled,\n",
+ bus->link_id);
+ return 0;
+ }
+ if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
+ amd_sdw_wake_enable(amd_manager, true);
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_host_wake_enable(amd_manager, true);
+ if (ret)
+ return ret;
+ }
+ ret = amd_sdw_clock_stop(amd_manager);
+ if (ret)
+ return ret;
+ } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
+ amd_sdw_wake_enable(amd_manager, true);
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_host_wake_enable(amd_manager, true);
+ if (ret)
+ return ret;
+ }
+ ret = amd_sdw_clock_stop(amd_manager);
+ if (ret)
+ return ret;
+ ret = amd_deinit_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ }
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3);
+ if (ret)
+ return ret;
+ if (amd_manager->wake_en_mask) {
+ val = readl(amd_manager->acp_mmio + ACP_PME_EN);
+ if (!val) {
+ writel(1, amd_manager->acp_mmio + ACP_PME_EN);
+ val = readl(amd_manager->acp_mmio + ACP_PME_EN);
+ dev_dbg(amd_manager->dev, "ACP_PME_EN:0x%x\n", val);
+ }
+ }
+ }
+ return 0;
+}
+
+static int __maybe_unused amd_resume_runtime(struct device *dev)
+{
+ struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev);
+ struct sdw_bus *bus = &amd_manager->bus;
+ int ret;
+ u32 val;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
+ ret = amd_sdw_clock_stop_exit(amd_manager);
+ if (ret)
+ return ret;
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_host_wake_enable(amd_manager, false);
+ if (ret)
+ return ret;
+ }
+ } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
+ writel(0x00, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance));
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_host_wake_enable(amd_manager, false);
+ if (ret)
+ return ret;
+ }
+ val = readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
+ if (val) {
+ val |= AMD_SDW_CLK_RESUME_REQ;
+ writel(val, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL, val,
+ (val & AMD_SDW_CLK_RESUME_DONE), ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+ if (val & AMD_SDW_CLK_RESUME_DONE) {
+ writel(0, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
+ amd_manager->clk_stopped = false;
+ }
+ }
+ sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
+ amd_init_sdw_manager(amd_manager);
+ amd_enable_sdw_interrupts(amd_manager);
+ ret = amd_enable_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ amd_sdw_set_frameshape(amd_manager);
+ }
+ if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
+ ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D0);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops amd_pm = {
+ .prepare = amd_pm_prepare,
+ SET_SYSTEM_SLEEP_PM_OPS(amd_suspend, amd_resume_runtime)
+ SET_RUNTIME_PM_OPS(amd_suspend_runtime, amd_resume_runtime, NULL)
+};
+
+static struct platform_driver amd_sdw_driver = {
+ .probe = &amd_sdw_manager_probe,
+ .remove = &amd_sdw_manager_remove,
+ .driver = {
+ .name = "amd_sdw_manager",
+ .pm = &amd_pm,
+ }
+};
+module_platform_driver(amd_sdw_driver);
+
+MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
+MODULE_DESCRIPTION("AMD SoundWire driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h
new file mode 100644
index 000000000000..6cc916b0c820
--- /dev/null
+++ b/drivers/soundwire/amd_manager.h
@@ -0,0 +1,277 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Copyright (C) 2023-24 Advanced Micro Devices, Inc. All rights reserved.
+ */
+
+#ifndef __AMD_MANAGER_H
+#define __AMD_MANAGER_H
+
+#include <linux/soundwire/sdw_amd.h>
+
+#define SDW_MANAGER_REG_OFFSET 0xc00
+#define AMD_SDW_DEFAULT_ROWS 50
+#define AMD_SDW_DEFAULT_COLUMNS 10
+#define ACP_PAD_PULLDOWN_CTRL 0x0001448
+#define ACP_SW_PAD_KEEPER_EN 0x0001454
+#define ACP_SW0_WAKE_EN 0x0001458
+#define ACP_EXTERNAL_INTR_CNTL0 0x0001a04
+#define ACP_EXTERNAL_INTR_STAT0 0x0001a0c
+#define ACP_EXTERNAL_INTR_CNTL(i) (ACP_EXTERNAL_INTR_CNTL0 + ((i) * 4))
+#define ACP_EXTERNAL_INTR_STAT(i) (ACP_EXTERNAL_INTR_STAT0 + ((i) * 4))
+#define ACP_SW_WAKE_EN(i) (ACP_SW0_WAKE_EN + ((i) * 8))
+
+#define ACP_SW_EN 0x0003000
+#define ACP_SW_EN_STATUS 0x0003004
+#define ACP_SW_FRAMESIZE 0x0003008
+#define ACP_SW_SSP_COUNTER 0x000300c
+#define ACP_SW_AUDIO0_TX_EN 0x0003010
+#define ACP_SW_AUDIO0_TX_EN_STATUS 0x0003014
+#define ACP_SW_AUDIO0_TX_FRAME_FORMAT 0x0003018
+#define ACP_SW_AUDIO0_TX_SAMPLEINTERVAL 0x000301c
+#define ACP_SW_AUDIO0_TX_HCTRL_DP0 0x0003020
+#define ACP_SW_AUDIO0_TX_HCTRL_DP1 0x0003024
+#define ACP_SW_AUDIO0_TX_HCTRL_DP2 0x0003028
+#define ACP_SW_AUDIO0_TX_HCTRL_DP3 0x000302c
+#define ACP_SW_AUDIO0_TX_OFFSET_DP0 0x0003030
+#define ACP_SW_AUDIO0_TX_OFFSET_DP1 0x0003034
+#define ACP_SW_AUDIO0_TX_OFFSET_DP2 0x0003038
+#define ACP_SW_AUDIO0_TX_OFFSET_DP3 0x000303c
+#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0 0x0003040
+#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP1 0x0003044
+#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP2 0x0003048
+#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP3 0x000304c
+#define ACP_SW_AUDIO1_TX_EN 0x0003050
+#define ACP_SW_AUDIO1_TX_EN_STATUS 0x0003054
+#define ACP_SW_AUDIO1_TX_FRAME_FORMAT 0x0003058
+#define ACP_SW_AUDIO1_TX_SAMPLEINTERVAL 0x000305c
+#define ACP_SW_AUDIO1_TX_HCTRL 0x0003060
+#define ACP_SW_AUDIO1_TX_OFFSET 0x0003064
+#define ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0 0x0003068
+#define ACP_SW_AUDIO2_TX_EN 0x000306c
+#define ACP_SW_AUDIO2_TX_EN_STATUS 0x0003070
+#define ACP_SW_AUDIO2_TX_FRAME_FORMAT 0x0003074
+#define ACP_SW_AUDIO2_TX_SAMPLEINTERVAL 0x0003078
+#define ACP_SW_AUDIO2_TX_HCTRL 0x000307c
+#define ACP_SW_AUDIO2_TX_OFFSET 0x0003080
+#define ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0 0x0003084
+#define ACP_SW_AUDIO0_RX_EN 0x0003088
+#define ACP_SW_AUDIO0_RX_EN_STATUS 0x000308c
+#define ACP_SW_AUDIO0_RX_FRAME_FORMAT 0x0003090
+#define ACP_SW_AUDIO0_RX_SAMPLEINTERVAL 0x0003094
+#define ACP_SW_AUDIO0_RX_HCTRL_DP0 0x0003098
+#define ACP_SW_AUDIO0_RX_HCTRL_DP1 0x000309c
+#define ACP_SW_AUDIO0_RX_HCTRL_DP2 0x0003100
+#define ACP_SW_AUDIO0_RX_HCTRL_DP3 0x0003104
+#define ACP_SW_AUDIO0_RX_OFFSET_DP0 0x0003108
+#define ACP_SW_AUDIO0_RX_OFFSET_DP1 0x000310c
+#define ACP_SW_AUDIO0_RX_OFFSET_DP2 0x0003110
+#define ACP_SW_AUDIO0_RX_OFFSET_DP3 0x0003114
+#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0 0x0003118
+#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP1 0x000311c
+#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP2 0x0003120
+#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP3 0x0003124
+#define ACP_SW_AUDIO1_RX_EN 0x0003128
+#define ACP_SW_AUDIO1_RX_EN_STATUS 0x000312c
+#define ACP_SW_AUDIO1_RX_FRAME_FORMAT 0x0003130
+#define ACP_SW_AUDIO1_RX_SAMPLEINTERVAL 0x0003134
+#define ACP_SW_AUDIO1_RX_HCTRL 0x0003138
+#define ACP_SW_AUDIO1_RX_OFFSET 0x000313c
+#define ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0 0x0003140
+#define ACP_SW_AUDIO2_RX_EN 0x0003144
+#define ACP_SW_AUDIO2_RX_EN_STATUS 0x0003148
+#define ACP_SW_AUDIO2_RX_FRAME_FORMAT 0x000314c
+#define ACP_SW_AUDIO2_RX_SAMPLEINTERVAL 0x0003150
+#define ACP_SW_AUDIO2_RX_HCTRL 0x0003154
+#define ACP_SW_AUDIO2_RX_OFFSET 0x0003158
+#define ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0 0x000315c
+#define ACP_SW_BPT_PORT_EN 0x0003160
+#define ACP_SW_BPT_PORT_EN_STATUS 0x0003164
+#define ACP_SW_BPT_PORT_FRAME_FORMAT 0x0003168
+#define ACP_SW_BPT_PORT_SAMPLEINTERVAL 0x000316c
+#define ACP_SW_BPT_PORT_HCTRL 0x0003170
+#define ACP_SW_BPT_PORT_OFFSET 0x0003174
+#define ACP_SW_BPT_PORT_CHANNEL_ENABLE 0x0003178
+#define ACP_SW_BPT_PORT_FIRST_BYTE_ADDR 0x000317c
+#define ACP_SW_CLK_RESUME_CTRL 0x0003180
+#define ACP_SW_CLK_RESUME_DELAY_CNTR 0x0003184
+#define ACP_SW_BUS_RESET_CTRL 0x0003188
+#define ACP_SW_PRBS_ERR_STATUS 0x000318c
+#define ACP_SW_IMM_CMD_UPPER_WORD 0x0003230
+#define ACP_SW_IMM_CMD_LOWER_QWORD 0x0003234
+#define ACP_SW_IMM_RESP_UPPER_WORD 0x0003238
+#define ACP_SW_IMM_RESP_LOWER_QWORD 0x000323c
+#define ACP_SW_IMM_CMD_STS 0x0003240
+#define ACP_SW_BRA_BASE_ADDRESS 0x0003244
+#define ACP_SW_BRA_TRANSFER_SIZE 0x0003248
+#define ACP_SW_BRA_DMA_BUSY 0x000324c
+#define ACP_SW_BRA_RESP 0x0003250
+#define ACP_SW_BRA_RESP_FRAME_ADDR 0x0003254
+#define ACP_SW_BRA_CURRENT_TRANSFER_SIZE 0x0003258
+#define ACP_SW_STATE_CHANGE_STATUS_0TO7 0x000325c
+#define ACP_SW_STATE_CHANGE_STATUS_8TO11 0x0003260
+#define ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7 0x0003264
+#define ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11 0x0003268
+#define ACP_SW_CLK_FREQUENCY_CTRL 0x000326c
+#define ACP_SW_ERROR_INTR_MASK 0x0003270
+#define ACP_SW_PHY_TEST_MODE_DATA_OFF 0x0003274
+
+#define ACP_DELAY_US 10
+#define AMD_SDW_TIMEOUT 1000
+#define AMD_SDW_DEFAULT_CLK_FREQ 12000000
+
+#define AMD_SDW_MCP_RESP_ACK BIT(0)
+#define AMD_SDW_MCP_RESP_NACK BIT(1)
+#define AMD_SDW_MCP_RESP_RDATA GENMASK(14, 7)
+
+#define AMD_SDW_MCP_CMD_SSP_TAG BIT(31)
+#define AMD_SDW_MCP_CMD_COMMAND GENMASK(14, 12)
+#define AMD_SDW_MCP_CMD_DEV_ADDR GENMASK(11, 8)
+#define AMD_SDW_MCP_CMD_REG_ADDR_HIGH GENMASK(7, 0)
+#define AMD_SDW_MCP_CMD_REG_ADDR_LOW GENMASK(31, 24)
+#define AMD_SDW_MCP_CMD_REG_DATA GENMASK(14, 7)
+#define AMD_SDW_MCP_SLAVE_STAT_0_3 GENMASK(14, 7)
+#define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK_ULL(39, 24)
+#define AMD_SDW_MCP_SLAVE_STATUS_MASK GENMASK(1, 0)
+#define AMD_SDW_MCP_SLAVE_STATUS_BITS GENMASK(3, 2)
+#define AMD_SDW_MCP_SLAVE_STATUS_8TO_11 GENMASK_ULL(15, 0)
+#define AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(x) BIT(((x) * 4))
+#define AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(x) (((x) * 4) + 1)
+
+#define AMD_SDW_MASTER_SUSPEND_DELAY_MS 2000
+#define AMD_SDW_QUIRK_MASK_BUS_ENABLE BIT(0)
+
+#define AMD_SDW_IMM_RES_VALID 1
+#define AMD_SDW_IMM_CMD_BUSY 2
+#define AMD_SDW_ENABLE 1
+#define AMD_SDW_DISABLE 0
+#define AMD_SDW_BUS_RESET_CLEAR_REQ 0
+#define AMD_SDW_BUS_RESET_REQ 1
+#define AMD_SDW_BUS_RESET_DONE 2
+#define AMD_SDW_BUS_BASE_FREQ 24000000
+
+#define AMD_SDW0_EXT_INTR_MASK 0x200000
+#define AMD_SDW1_EXT_INTR_MASK 4
+#define AMD_SDW_IRQ_MASK_0TO7 0x77777777
+#define AMD_SDW_IRQ_MASK_8TO11 0x000c7777
+#define AMD_SDW_IRQ_ERROR_MASK 0xff
+#define AMD_SDW_MAX_FREQ_NUM 1
+#define AMD_ACP63_SDW0_MAX_TX_PORTS 3
+#define AMD_ACP63_SDW0_MAX_RX_PORTS 3
+#define AMD_ACP63_SDW1_MAX_TX_PORTS 1
+#define AMD_ACP63_SDW1_MAX_RX_PORTS 1
+#define AMD_ACP70_SDW_MAX_TX_PORTS 3
+#define AMD_ACP70_SDW_MAX_RX_PORTS 3
+#define AMD_ACP63_SDW0_MAX_DAI 6
+#define AMD_ACP63_SDW1_MAX_DAI 2
+#define AMD_ACP70_SDW_MAX_DAI 6
+#define AMD_SDW_SLAVE_0_ATTACHED 5
+#define AMD_SDW_SSP_COUNTER_VAL 3
+
+#define AMD_DPN_FRAME_FMT_PFM GENMASK(1, 0)
+#define AMD_DPN_FRAME_FMT_PDM GENMASK(3, 2)
+#define AMD_DPN_FRAME_FMT_BLK_PKG_MODE BIT(4)
+#define AMD_DPN_FRAME_FMT_BLK_GRP_CTRL GENMASK(6, 5)
+#define AMD_DPN_FRAME_FMT_WORD_LEN GENMASK(12, 7)
+#define AMD_DPN_FRAME_FMT_PCM_OR_PDM BIT(13)
+#define AMD_DPN_HCTRL_HSTOP GENMASK(3, 0)
+#define AMD_DPN_HCTRL_HSTART GENMASK(7, 4)
+#define AMD_DPN_OFFSET_CTRL_1 GENMASK(7, 0)
+#define AMD_DPN_OFFSET_CTRL_2 GENMASK(15, 8)
+#define AMD_DPN_CH_EN_LCTRL GENMASK(2, 0)
+#define AMD_DPN_CH_EN_CHMASK GENMASK(10, 3)
+#define AMD_SDW_STAT_MAX_RETRY_COUNT 100
+#define AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7f9f
+#define AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7ffa
+#define AMD_SDW0_PAD_PULLDOWN_CTRL_DISABLE_MASK 0x60
+#define AMD_SDW1_PAD_PULLDOWN_CTRL_DISABLE_MASK 5
+#define AMD_SDW0_PAD_KEEPER_EN_MASK 1
+#define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10
+#define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1e
+#define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xf
+#define AMD_SDW_PREQ_INTR_STAT BIT(19)
+#define AMD_SDW_CLK_STOP_DONE 1
+#define AMD_SDW_CLK_RESUME_REQ 2
+#define AMD_SDW_CLK_RESUME_DONE 3
+#define AMD_SDW_WAKE_STAT_MASK BIT(16)
+#define AMD_SDW_WAKE_INTR_MASK BIT(16)
+#define AMD_SDW0_HOST_WAKE_INTR_MASK BIT(22)
+#define AMD_SDW1_HOST_WAKE_INTR_MASK BIT(23)
+#define AMD_SDW_DEVICE_STATE 0x1430
+#define AMD_SDW0_DEVICE_STATE_MASK GENMASK(1, 0)
+#define AMD_SDW1_DEVICE_STATE_MASK GENMASK(3, 2)
+#define AMD_SDW_DEVICE_STATE_D0 0
+#define AMD_SDW_DEVICE_STATE_D3 3
+#define ACP_PME_EN 0x0001400
+
+static u32 amd_sdw_freq_tbl[AMD_SDW_MAX_FREQ_NUM] = {
+ AMD_SDW_DEFAULT_CLK_FREQ,
+};
+
+struct sdw_manager_dp_reg {
+ u32 frame_fmt_reg;
+ u32 sample_int_reg;
+ u32 hctrl_dp0_reg;
+ u32 offset_reg;
+ u32 lane_ctrl_ch_en_reg;
+};
+
+/*
+ * SDW0 Manager instance registers 6 CPU DAI (3 TX & 3 RX Ports)
+ * whereas SDW1 Manager Instance registers 2 CPU DAI (one TX & one RX port)
+ * Below is the CPU DAI <->Manager port number mapping
+ * i.e SDW0 Pin0 -> port number 0 -> AUDIO0 TX
+ * SDW0 Pin1 -> Port number 1 -> AUDIO1 TX
+ * SDW0 Pin2 -> Port number 2 -> AUDIO2 TX
+ * SDW0 Pin3 -> port number 3 -> AUDIO0 RX
+ * SDW0 Pin4 -> Port number 4 -> AUDIO1 RX
+ * SDW0 Pin5 -> Port number 5 -> AUDIO2 RX
+ * Whereas for SDW1 instance
+ * SDW1 Pin0 -> port number 0 -> AUDIO1 TX
+ * SDW1 Pin1 -> Port number 1 -> AUDIO1 RX
+ * Same mapping should be used for programming DMA controller registers in SoundWire DMA driver.
+ * i.e if AUDIO0 TX channel is selected then we need to use AUDIO0 TX registers for DMA programming
+ * in SoundWire DMA driver.
+ */
+
+static struct sdw_manager_dp_reg acp63_sdw0_dp_reg[AMD_ACP63_SDW0_MAX_DAI] = {
+ {ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0,
+ ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL,
+ ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO2_TX_FRAME_FORMAT, ACP_SW_AUDIO2_TX_SAMPLEINTERVAL, ACP_SW_AUDIO2_TX_HCTRL,
+ ACP_SW_AUDIO2_TX_OFFSET, ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO0_RX_FRAME_FORMAT, ACP_SW_AUDIO0_RX_SAMPLEINTERVAL, ACP_SW_AUDIO0_RX_HCTRL_DP0,
+ ACP_SW_AUDIO0_RX_OFFSET_DP0, ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
+ ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO2_RX_FRAME_FORMAT, ACP_SW_AUDIO2_RX_SAMPLEINTERVAL, ACP_SW_AUDIO2_RX_HCTRL,
+ ACP_SW_AUDIO2_RX_OFFSET, ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0},
+};
+
+static struct sdw_manager_dp_reg acp63_sdw1_dp_reg[AMD_ACP63_SDW1_MAX_DAI] = {
+ {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL,
+ ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
+ ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0}
+};
+
+static struct sdw_manager_dp_reg acp70_sdw_dp_reg[AMD_ACP70_SDW_MAX_DAI] = {
+ {ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0,
+ ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL,
+ ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO2_TX_FRAME_FORMAT, ACP_SW_AUDIO2_TX_SAMPLEINTERVAL, ACP_SW_AUDIO2_TX_HCTRL,
+ ACP_SW_AUDIO2_TX_OFFSET, ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO0_RX_FRAME_FORMAT, ACP_SW_AUDIO0_RX_SAMPLEINTERVAL, ACP_SW_AUDIO0_RX_HCTRL_DP0,
+ ACP_SW_AUDIO0_RX_OFFSET_DP0, ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
+ ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0},
+ {ACP_SW_AUDIO2_RX_FRAME_FORMAT, ACP_SW_AUDIO2_RX_SAMPLEINTERVAL, ACP_SW_AUDIO2_RX_HCTRL,
+ ACP_SW_AUDIO2_RX_OFFSET, ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0},
+};
+
+static u32 sdw_manager_reg_mask_array[AMD_SDW_MAX_MANAGER_COUNT] = {
+ AMD_SDW0_EXT_INTR_MASK,
+ AMD_SDW1_EXT_INTR_MASK
+};
+#endif
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c
index 1cbfedfc20ef..55c1db816534 100644
--- a/drivers/soundwire/bus.c
+++ b/drivers/soundwire/bus.c
@@ -2,54 +2,111 @@
// 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 <linux/string_choices.h>
#include "bus.h"
+#include "irq.h"
+#include "sysfs_local.h"
+
+static DEFINE_IDA(sdw_bus_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;
+
+ if (bus->controller_id == -1)
+ bus->controller_id = rc;
+
+ return 0;
+}
/**
- * sdw_add_bus_master() - add a bus Master instance
+ * 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_add_bus_master(struct sdw_bus *bus)
+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 (!bus->dev) {
- pr_err("SoundWire bus has no device");
+ 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;
+ }
+
+ ida_init(&bus->slave_ida);
+
+ 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");
+ dev_err(bus->dev, "SoundWire Bus ops are not set\n");
return -EINVAL;
}
- mutex_init(&bus->msg_lock);
- mutex_init(&bus->bus_lock);
+ if (!bus->compute_params) {
+ dev_err(bus->dev,
+ "Bandwidth allocation not configured, compute_params no set\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Give each bus_lock and msg_lock a unique key so that lockdep won't
+ * trigger a deadlock warning when the locks of several buses are
+ * grabbed during configuration of a multi-bus stream.
+ */
+ lockdep_register_key(&bus->msg_lock_key);
+ __mutex_init(&bus->msg_lock, "msg_lock", &bus->msg_lock_key);
+
+ lockdep_register_key(&bus->bus_lock_key);
+ __mutex_init(&bus->bus_lock, "bus_lock", &bus->bus_lock_key);
+
INIT_LIST_HEAD(&bus->slaves);
INIT_LIST_HEAD(&bus->m_rt_list);
/*
* Initialize multi_link flag
- * TODO: populate this flag by reading property from FW node
*/
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", ret);
+ dev_err(bus->dev,
+ "Bus read properties failed:%d\n", ret);
return ret;
}
}
+ sdw_bus_debugfs_init(bus);
+
/*
- * Device numbers in SoundWire are 0 thru 15. Enumeration device
+ * 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.
@@ -58,7 +115,7 @@ int sdw_add_bus_master(struct sdw_bus *bus)
/* Set higher order bits */
*bus->assigned = ~GENMASK(SDW_BROADCAST_DEV_NUM, SDW_ENUM_DEV_NUM);
- /* Set enumuration device number and broadcast device number */
+ /* Set enumeration device number and broadcast device number */
set_bit(SDW_ENUM_DEV_NUM, bus->assigned);
set_bit(SDW_BROADCAST_DEV_NUM, bus->assigned);
@@ -67,6 +124,10 @@ int sdw_add_bus_master(struct sdw_bus *bus)
set_bit(SDW_GROUP13_DEV_NUM, bus->assigned);
set_bit(SDW_MASTER_DEV_NUM, bus->assigned);
+ ret = sdw_irq_create(bus, fwnode);
+ if (ret)
+ return ret;
+
/*
* SDW is an enumerable bus, but devices can be powered off. So,
* they won't be able to report as present.
@@ -76,17 +137,20 @@ int sdw_add_bus_master(struct sdw_bus *bus)
*/
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) {
+ if (ret < 0) {
dev_err(bus->dev, "Finding slaves failed:%d\n", ret);
+ sdw_irq_delete(bus);
return ret;
}
/*
* Initialize clock values based on Master properties. The max
- * frequency is read from max_freq property. Current assumption
+ * frequency is read from max_clk_freq property. Current assumption
* is that the bus will start at highest clock frequency when
* powered on.
*
@@ -94,25 +158,31 @@ int sdw_add_bus_master(struct sdw_bus *bus)
* to start with bank 0 (Table 40 of Spec)
*/
prop = &bus->prop;
- bus->params.max_dr_freq = prop->max_freq * SDW_DOUBLE_RATE_FACTOR;
+ 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_add_bus_master);
+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 */
+ if (slave->dev_num) { /* clear dev_num if assigned */
clear_bit(slave->dev_num, bus->assigned);
-
+ if (bus->ops && bus->ops->put_device_num)
+ bus->ops->put_device_num(bus, slave);
+ }
list_del_init(&slave->node);
mutex_unlock(&bus->bus_lock);
@@ -121,16 +191,25 @@ static int sdw_delete_slave(struct device *dev, void *data)
}
/**
- * sdw_delete_bus_master() - delete the bus master instance
+ * sdw_bus_master_delete() - 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)
+void sdw_bus_master_delete(struct sdw_bus *bus)
{
device_for_each_child(bus->dev, NULL, sdw_delete_slave);
+
+ sdw_irq_delete(bus);
+
+ sdw_master_device_del(bus);
+
+ sdw_bus_debugfs_exit(bus);
+ lockdep_unregister_key(&bus->bus_lock_key);
+ lockdep_unregister_key(&bus->msg_lock_key);
+ ida_free(&sdw_bus_ida, bus->id);
}
-EXPORT_SYMBOL(sdw_delete_bus_master);
+EXPORT_SYMBOL(sdw_bus_master_delete);
/*
* SDW IO Calls
@@ -172,8 +251,9 @@ static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg)
}
static inline int do_transfer_defer(struct sdw_bus *bus,
- struct sdw_msg *msg, struct sdw_defer *defer)
+ struct sdw_msg *msg)
{
+ struct sdw_defer *defer = &bus->defer_msg;
int retry = bus->prop.err_threshold;
enum sdw_command_response resp;
int ret = 0, i;
@@ -183,7 +263,7 @@ static inline int do_transfer_defer(struct sdw_bus *bus,
init_completion(&defer->complete);
for (i = 0; i <= retry; i++) {
- resp = bus->ops->xfer_msg_defer(bus, msg, defer);
+ resp = bus->ops->xfer_msg_defer(bus);
ret = find_response_code(resp);
/* if cmd is ok or ignored return */
if (ret == 0 || ret == -ENODATA)
@@ -193,19 +273,16 @@ static inline int do_transfer_defer(struct sdw_bus *bus,
return ret;
}
-static int sdw_reset_page(struct sdw_bus *bus, u16 dev_num)
+static int sdw_transfer_unlocked(struct sdw_bus *bus, struct sdw_msg *msg)
{
- int retry = bus->prop.err_threshold;
- enum sdw_command_response resp;
- int ret = 0, i;
+ int ret;
- 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;
- }
+ 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,
+ str_write_read(msg->flags & SDW_MSG_FLAG_WRITE),
+ msg->addr, msg->len);
return ret;
}
@@ -221,13 +298,7 @@ int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg)
mutex_lock(&bus->msg_lock);
- ret = do_transfer(bus, msg);
- if (ret != 0 && ret != -ENODATA)
- dev_err(bus->dev, "trf on Slave %d failed:%d\n",
- msg->dev_num, ret);
-
- if (msg->page)
- sdw_reset_page(bus, msg->dev_num);
+ ret = sdw_transfer_unlocked(bus, msg);
mutex_unlock(&bus->msg_lock);
@@ -235,35 +306,61 @@ int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg)
}
/**
+ * 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 sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg)
{
int ret;
if (!bus->ops->xfer_msg_defer)
return -ENOTSUPP;
- ret = do_transfer_defer(bus, msg, defer);
+ ret = do_transfer_defer(bus, msg);
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);
+ msg->dev_num, ret);
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)
+ 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 */
@@ -271,12 +368,11 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
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 */
+ if (addr < SDW_REG_NO_PAGE) /* no paging area */
return 0;
- } else if (addr >= SDW_REG_MAX) { /* illegal addr */
+
+ if (addr >= SDW_REG_MAX) { /* illegal addr */
pr_err("SDW: Invalid address %x passed\n", addr);
return -EINVAL;
}
@@ -284,7 +380,7 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
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 */
+ /* no need for else as that will fall-through to paging */
}
/* paging mandatory */
@@ -296,43 +392,234 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
if (!slave) {
pr_err("SDW: No slave for paging addr\n");
return -EINVAL;
- } else if (!slave->prop.paging_support) {
+ }
+
+ if (!slave->prop.paging_support) {
dev_err(&slave->dev,
- "address %x needs paging but no support", addr);
+ "address %x needs paging but no support\n", 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_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.
+ */
+
+static int sdw_ntransfer_no_pm(struct sdw_slave *slave, u32 addr, u8 flags,
+ size_t count, u8 *val)
+{
+ struct sdw_msg msg;
+ size_t size;
+ int ret;
+
+ while (count) {
+ // Only handle bytes up to next page boundary
+ size = min_t(size_t, count, (SDW_REGADDR + 1) - (addr & SDW_REGADDR));
+
+ ret = sdw_fill_msg(&msg, slave, addr, size, slave->dev_num, flags, val);
+ if (ret < 0)
+ return ret;
+
+ ret = sdw_transfer(slave->bus, &msg);
+ if (ret < 0 && !slave->is_mockup_device)
+ return ret;
+
+ addr += size;
+ val += size;
+ count -= size;
+ }
+
+ return 0;
+}
+
/**
- * sdw_nread() - Read "n" contiguous SDW Slave registers
+ * sdw_nread_no_pm() - Read "n" contiguous SDW Slave registers with no PM
* @slave: SDW Slave
* @addr: Register address
* @count: length
* @val: Buffer for values to be read
+ *
+ * Note that if the message crosses a page boundary each page will be
+ * transferred under a separate invocation of the msg_lock.
*/
-int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
+int sdw_nread_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
+{
+ return sdw_ntransfer_no_pm(slave, addr, SDW_MSG_FLAG_READ, count, val);
+}
+EXPORT_SYMBOL(sdw_nread_no_pm);
+
+/**
+ * sdw_nwrite_no_pm() - Write "n" contiguous SDW Slave registers with no PM
+ * @slave: SDW Slave
+ * @addr: Register address
+ * @count: length
+ * @val: Buffer for values to be written
+ *
+ * Note that if the message crosses a page boundary each page will be
+ * transferred under a separate invocation of the msg_lock.
+ */
+int sdw_nwrite_no_pm(struct sdw_slave *slave, u32 addr, size_t count, const u8 *val)
+{
+ return sdw_ntransfer_no_pm(slave, addr, SDW_MSG_FLAG_WRITE, count, (u8 *)val);
+}
+EXPORT_SYMBOL(sdw_nwrite_no_pm);
+
+/**
+ * sdw_write_no_pm() - Write a SDW Slave register with no PM
+ * @slave: SDW Slave
+ * @addr: Register address
+ * @value: Register value
+ */
+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, slave, addr, count,
- slave->dev_num, SDW_MSG_FLAG_READ, val);
+ 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;
- ret = pm_runtime_get_sync(slave->bus->dev);
+ 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;
- ret = sdw_transfer(slave->bus, &msg);
- pm_runtime_put(slave->bus->dev);
+ 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);
+
+/**
+ * sdw_read_no_pm() - Read a SDW Slave register with no PM
+ * @slave: SDW Slave
+ * @addr: Register address
+ */
+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
+ *
+ * This version of the function will take a PM reference to the slave
+ * device.
+ * Note that if the message crosses a page boundary each page will be
+ * transferred under a separate invocation of the msg_lock.
+ */
+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;
}
@@ -343,24 +630,27 @@ EXPORT_SYMBOL(sdw_nread);
* @slave: SDW Slave
* @addr: Register address
* @count: length
- * @val: Buffer for values to be read
+ * @val: Buffer for values to be written
+ *
+ * This version of the function will take a PM reference to the slave
+ * device.
+ * Note that if the message crosses a page boundary each page will be
+ * transferred under a separate invocation of the msg_lock.
*/
-int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
+int sdw_nwrite(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, val);
- if (ret < 0)
+ ret = pm_runtime_get_sync(&slave->dev);
+ if (ret < 0 && ret != -EACCES) {
+ pm_runtime_put_noidle(&slave->dev);
return ret;
+ }
- ret = pm_runtime_get_sync(slave->bus->dev);
- if (ret < 0)
- return ret;
+ ret = sdw_nwrite_no_pm(slave, addr, count, val);
- ret = sdw_transfer(slave->bus, &msg);
- pm_runtime_put(slave->bus->dev);
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put(&slave->dev);
return ret;
}
@@ -370,6 +660,9 @@ EXPORT_SYMBOL(sdw_nwrite);
* sdw_read() - Read a SDW Slave register
* @slave: SDW Slave
* @addr: Register address
+ *
+ * This version of the function will take a PM reference to the slave
+ * device.
*/
int sdw_read(struct sdw_slave *slave, u32 addr)
{
@@ -379,8 +672,8 @@ int sdw_read(struct sdw_slave *slave, u32 addr)
ret = sdw_nread(slave, addr, 1, &buf);
if (ret < 0)
return ret;
- else
- return buf;
+
+ return buf;
}
EXPORT_SYMBOL(sdw_read);
@@ -389,11 +682,13 @@ EXPORT_SYMBOL(sdw_read);
* @slave: SDW Slave
* @addr: Register address
* @value: Register value
+ *
+ * This version of the function will take a PM reference to the slave
+ * device.
*/
int sdw_write(struct sdw_slave *slave, u32 addr, u8 value)
{
return sdw_nwrite(slave, addr, 1, &value);
-
}
EXPORT_SYMBOL(sdw_write);
@@ -404,7 +699,7 @@ EXPORT_SYMBOL(sdw_write);
/* called with bus_lock held */
static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i)
{
- struct sdw_slave *slave = NULL;
+ struct sdw_slave *slave;
list_for_each_entry(slave, &bus->slaves, node) {
if (slave->dev_num == i)
@@ -414,34 +709,42 @@ static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i)
return NULL;
}
-static int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id)
+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))
+ 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)
{
+ struct sdw_bus *bus = slave->bus;
int bit;
- bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES);
- if (bit == SDW_MAX_DEVICES) {
- bit = -ENODEV;
- goto err;
+ if (bus->ops && bus->ops->get_device_num) {
+ bit = bus->ops->get_device_num(bus, slave);
+ if (bit < 0)
+ goto err;
+ } else {
+ bit = find_first_zero_bit(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);
+ set_bit(bit, bus->assigned);
err:
return bit;
@@ -449,89 +752,98 @@ err:
static int sdw_assign_device_num(struct sdw_slave *slave)
{
- int ret, dev_num;
+ struct sdw_bus *bus = slave->bus;
+ struct device *dev = bus->dev;
+ int ret;
/* 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;
+ if (!slave->dev_num_sticky) {
+ int 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(dev, "Get dev_num failed: %d\n", dev_num);
+ return dev_num;
+ }
+ slave->dev_num_sticky = dev_num;
+ } else {
+ dev_dbg(dev, "Slave already registered, reusing dev_num: %d\n",
+ slave->dev_num_sticky);
+ }
}
- ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num);
+ /* Clear the slave->dev_num to transfer message on device 0 */
+ slave->dev_num = 0;
+
+ ret = sdw_write_no_pm(slave, SDW_SCP_DEVNUMBER, slave->dev_num_sticky);
if (ret < 0) {
- dev_err(&slave->dev, "Program device_num failed: %d", ret);
+ dev_err(dev, "Program device_num %d failed: %d\n",
+ slave->dev_num_sticky, ret);
return ret;
}
/* After xfer of msg, restore dev_num */
- slave->dev_num = dev_num;
+ slave->dev_num = slave->dev_num_sticky;
+
+ if (bus->ops && bus->ops->new_peripheral_assigned)
+ bus->ops->new_peripheral_assigned(bus, slave, slave->dev_num);
return 0;
}
void sdw_extract_slave_id(struct sdw_bus *bus,
- u64 addr, struct sdw_slave_id *id)
+ u64 addr, struct sdw_slave_id *id)
{
- dev_dbg(bus->dev, "SDW Slave Addr: %llx", addr);
+ dev_dbg(bus->dev, "SDW Slave Addr: %llx\n", 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);
+ 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 %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);
+ "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);
+bool is_clock_scaling_supported_by_slave(struct sdw_slave *slave)
+{
+ /*
+ * Dynamic scaling is a defined by SDCA. However, some devices expose the class ID but
+ * can't support dynamic scaling. We might need a quirk to handle such devices.
+ */
+ return slave->id.class_id;
}
+EXPORT_SYMBOL(is_clock_scaling_supported_by_slave);
-static int sdw_program_device_num(struct sdw_bus *bus)
+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 = false;
+ 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);
+ 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;
}
@@ -550,32 +862,54 @@ static int sdw_program_device_num(struct sdw_bus *bus)
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) {
- dev_err(slave->bus->dev,
- "Assign dev_num failed:%d",
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Assign dev_num failed:%d\n",
ret);
return ret;
}
+ *programmed = true;
+
break;
}
}
- if (found == false) {
+ if (!found) {
/* TODO: Park this device in Group 13 */
- dev_err(bus->dev, "Slave Entry not found");
+
+ /*
+ * 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++;
@@ -592,20 +926,346 @@ static int sdw_program_device_num(struct sdw_bus *bus)
}
static void sdw_modify_slave_status(struct sdw_slave *slave,
- enum sdw_slave_status status)
+ enum sdw_slave_status status)
{
- mutex_lock(&slave->bus->bus_lock);
+ 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(&slave->bus->bus_lock);
+ 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, bool prepare)
+{
+ 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 %s done slave:%d\n",
+ prepare ? "prepare" : "deprepare",
+ dev_num);
+ return 0;
+ }
+
+ usleep_range(1000, 1500);
+ retry--;
+ } while (retry);
+
+ dev_dbg(bus->dev, "clock stop %s did not complete for slave:%d\n",
+ prepare ? "prepare" : "deprepare",
+ 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, true);
+ /*
+ * 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, false);
+ 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)
+ 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",
+ str_on_off(enable));
+ mask |= SDW_DPN_INT_TEST_FAIL;
+ }
+
addr = SDW_DPN_INTMASK(port);
/* Set/Clear port ready interrupt mask */
@@ -617,10 +1277,139 @@ int sdw_configure_dpn_intr(struct sdw_slave *slave,
val &= ~SDW_DPN_INT_PORT_READY;
}
- ret = sdw_update(slave, addr, (mask | SDW_DPN_INT_PORT_READY), val);
+ ret = sdw_update_no_pm(slave, addr, (mask | SDW_DPN_INT_PORT_READY), val);
if (ret < 0)
- dev_err(slave->bus->dev,
- "SDW_DPN_INTMASK write failed:%d", val);
+ dev_err(&slave->dev,
+ "SDW_DPN_INTMASK write failed:%d\n", val);
+
+ return ret;
+}
+
+int sdw_slave_get_scale_index(struct sdw_slave *slave, u8 *base)
+{
+ u32 mclk_freq = slave->bus->prop.mclk_freq;
+ u32 curr_freq = slave->bus->params.curr_dr_freq >> 1;
+ unsigned int scale;
+ u8 scale_index;
+
+ 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 (!(22579200 % mclk_freq)) {
+ mclk_freq = 22579200;
+ *base = SDW_SCP_BASE_CLOCK_22579200_HZ;
+ } else if (!(24576000 % mclk_freq)) {
+ mclk_freq = 24576000;
+ *base = SDW_SCP_BASE_CLOCK_24576000_HZ;
+ } else if (!(32000000 % mclk_freq)) {
+ mclk_freq = 32000000;
+ *base = SDW_SCP_BASE_CLOCK_32000000_HZ;
+ } else if (!(96000000 % mclk_freq)) {
+ mclk_freq = 24000000;
+ *base = SDW_SCP_BASE_CLOCK_24000000_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++;
+
+ dev_dbg(&slave->dev,
+ "Configured bus base %d, scale %d, mclk %d, curr_freq %d\n",
+ *base, scale_index, mclk_freq, curr_freq);
+
+ return scale_index;
+}
+EXPORT_SYMBOL(sdw_slave_get_scale_index);
+
+int sdw_slave_get_current_bank(struct sdw_slave *slave)
+{
+ int tmp;
+
+ tmp = sdw_read(slave, SDW_SCP_CTRL);
+ if (tmp < 0)
+ return tmp;
+
+ return FIELD_GET(SDW_SCP_STAT_CURR_BANK, tmp);
+}
+EXPORT_SYMBOL_GPL(sdw_slave_get_current_bank);
+
+static int sdw_slave_set_frequency(struct sdw_slave *slave)
+{
+ int 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.
+ * Driver can set the property directly, for now there's no
+ * DisCo property to discover support for the scaling registers
+ * from platform firmware.
+ */
+ if (!slave->id.class_id && !slave->prop.clock_reg_supported)
+ return 0;
+
+ scale_index = sdw_slave_get_scale_index(slave, &base);
+ if (scale_index < 0)
+ return 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);
return ret;
}
@@ -628,60 +1417,101 @@ int sdw_configure_dpn_intr(struct sdw_slave *slave,
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) &&
+ !(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 bus clash, parity and SCP implementation
- * defined interrupt mask
- * TODO: Read implementation defined interrupt mask
- * from Slave property
+ * Set SCP_INT1_MASK register, typically bus clash and
+ * implementation-defined interrupt mask. The Parity detection
+ * may not always be correct on startup so its use is
+ * device-dependent, it might e.g. only be enabled in
+ * steady-state after a couple of frames.
*/
- val = SDW_SCP_INT1_IMPL_DEF | SDW_SCP_INT1_BUS_CLASH |
- SDW_SCP_INT1_PARITY;
+ val = prop->scp_int1_mask;
/* Enable SCP interrupts */
- ret = sdw_update(slave, SDW_SCP_INTMASK1, val, val);
+ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, val, val);
if (ret < 0) {
- dev_err(slave->bus->dev,
- "SDW_SCP_INTMASK1 write failed:%d", ret);
+ 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)
+ if (!prop->dp0_prop)
return 0;
/* Enable DP0 interrupts */
- val = prop->dp0_prop->device_interrupts;
+ val = prop->dp0_prop->imp_def_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;
+ 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 = 0, impl_int_mask;
+ u8 clear, impl_int_mask;
int status, status2, ret, count = 0;
- status = sdw_read(slave, SDW_DP0_INT);
+ status = sdw_read_no_pm(slave, SDW_DP0_INT);
if (status < 0) {
- dev_err(slave->bus->dev,
- "SDW_DP0_INT read failed:%d", status);
+ dev_err(&slave->dev,
+ "SDW_DP0_INT read failed:%d\n", status);
return status;
}
do {
+ clear = status & ~(SDW_DP0_INTERRUPTS | SDW_DP0_SDCA_CASCADE);
if (status & SDW_DP0_INT_TEST_FAIL) {
- dev_err(&slave->dev, "Test fail for port 0");
+ dev_err(&slave->dev, "Test fail for port 0\n");
clear |= SDW_DP0_INT_TEST_FAIL;
}
@@ -696,7 +1526,7 @@ static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status)
}
if (status & SDW_DP0_INT_BRA_FAILURE) {
- dev_err(&slave->dev, "BRA failed");
+ dev_err(&slave->dev, "BRA failed\n");
clear |= SDW_DP0_INT_BRA_FAILURE;
}
@@ -708,38 +1538,39 @@ static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status)
*slave_status = clear;
}
- /* clear the interrupt */
- ret = sdw_write(slave, SDW_DP0_INT, 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->bus->dev,
- "SDW_DP0_INT write failed:%d", ret);
+ dev_err(&slave->dev,
+ "SDW_DP0_INT write failed:%d\n", ret);
return ret;
}
/* Read DP0 interrupt again */
- status2 = sdw_read(slave, SDW_DP0_INT);
+ status2 = sdw_read_no_pm(slave, SDW_DP0_INT);
if (status2 < 0) {
- dev_err(slave->bus->dev,
- "SDW_DP0_INT read failed:%d", status2);
+ 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 != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
+ } while ((status & SDW_DP0_INTERRUPTS) && (count < SDW_READ_INTR_CLEAR_RETRY));
if (count == SDW_READ_INTR_CLEAR_RETRY)
- dev_warn(slave->bus->dev, "Reached MAX_RETRY on DP0 read");
+ 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)
+ int port, u8 *slave_status)
{
- u8 clear = 0, impl_int_mask;
+ u8 clear, impl_int_mask;
int status, status2, ret, count = 0;
u32 addr;
@@ -747,18 +1578,19 @@ static int sdw_handle_port_interrupt(struct sdw_slave *slave,
return sdw_handle_dp0_interrupt(slave, slave_status);
addr = SDW_DPN_INT(port);
- status = sdw_read(slave, addr);
+ status = sdw_read_no_pm(slave, addr);
if (status < 0) {
- dev_err(slave->bus->dev,
- "SDW_DPN_INT read failed:%d", status);
+ 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", port);
+ dev_err(&slave->dev, "Test fail for port:%d\n", port);
clear |= SDW_DPN_INT_TEST_FAIL;
}
@@ -774,36 +1606,36 @@ static int sdw_handle_port_interrupt(struct sdw_slave *slave,
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);
+ /* clear the interrupt but don't touch reserved fields */
+ ret = sdw_write_no_pm(slave, addr, clear);
if (ret < 0) {
- dev_err(slave->bus->dev,
- "SDW_DPN_INT write failed:%d", ret);
+ dev_err(&slave->dev,
+ "SDW_DPN_INT write failed:%d\n", ret);
return ret;
}
/* Read DPN interrupt again */
- status2 = sdw_read(slave, addr);
+ status2 = sdw_read_no_pm(slave, addr);
if (status2 < 0) {
- dev_err(slave->bus->dev,
- "SDW_DPN_INT read failed:%d", status2);
+ 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 != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
+ } while ((status & SDW_DPN_INTERRUPTS) && (count < SDW_READ_INTR_CLEAR_RETRY));
if (count == SDW_READ_INTR_CLEAR_RETRY)
- dev_warn(slave->bus->dev, "Reached MAX_RETRY on port read");
+ dev_warn(&slave->dev, "Reached MAX_RETRY on port read");
return ret;
}
@@ -811,41 +1643,70 @@ static int sdw_handle_port_interrupt(struct sdw_slave *slave,
static int sdw_handle_slave_alerts(struct sdw_slave *slave)
{
struct sdw_slave_intr_status slave_intr;
- u8 clear = 0, bit, port_status[15];
+ u8 clear = 0, bit, port_status[15] = {0};
int port_num, stat, ret, count = 0;
unsigned long port;
- bool slave_notify = false;
- u8 buf, buf2[2], _buf, _buf2[2];
+ bool slave_notify;
+ u8 sdca_cascade = 0;
+ u8 buf, buf2[2];
+ bool parity_check;
+ bool parity_quirk;
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);
+ 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;
}
- ret = sdw_nread(slave, SDW_SCP_INTSTAT2, 2, buf2);
+ /* Read Intstat 1, Intstat 2 and Intstat 3 registers */
+ ret = sdw_read_no_pm(slave, SDW_SCP_INT1);
if (ret < 0) {
- dev_err(slave->bus->dev,
- "SDW_SCP_INT2/3 read failed:%d", ret);
- return ret;
+ 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->id.class_id) {
+ 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) {
- dev_err(&slave->dev, "Parity error detected");
+ parity_check = slave->prop.scp_int1_mask & SDW_SCP_INT1_PARITY;
+ parity_quirk = !slave->first_interrupt_done &&
+ (slave->prop.quirks & SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY);
+
+ if (parity_check && !parity_quirk)
+ dev_err(&slave->dev, "Parity error detected\n");
clear |= SDW_SCP_INT1_PARITY;
}
if (buf & SDW_SCP_INT1_BUS_CLASH) {
- dev_err(&slave->dev, "Bus clash error detected");
+ 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;
}
@@ -857,20 +1718,25 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
*/
if (buf & SDW_SCP_INT1_IMPL_DEF) {
- dev_dbg(&slave->dev, "Slave impl defined interrupt\n");
+ if (slave->prop.scp_int1_mask & SDW_SCP_INT1_IMPL_DEF) {
+ dev_dbg(&slave->dev, "Slave impl defined interrupt\n");
+ slave_notify = true;
+ }
clear |= SDW_SCP_INT1_IMPL_DEF;
- slave_notify = true;
}
+ /* 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 = port >> SDW_REG_SHIFT(SDW_SCP_INT1_PORT0_3);
+ port = FIELD_GET(SDW_SCP_INT1_PORT0_3, port);
for_each_set_bit(bit, &port, 8) {
sdw_handle_port_interrupt(slave, bit,
- &port_status[bit]);
-
+ &port_status[bit]);
}
/* Check if cascade 2 interrupt is present */
@@ -878,7 +1744,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
port = buf2[0] & SDW_SCP_INTSTAT2_PORT4_10;
for_each_set_bit(bit, &port, 8) {
/* scp2 ports start from 4 */
- port_num = bit + 3;
+ port_num = bit + 4;
sdw_handle_port_interrupt(slave,
port_num,
&port_status[port_num]);
@@ -890,7 +1756,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
port = buf2[1] & SDW_SCP_INTSTAT3_PORT11_14;
for_each_set_bit(bit, &port, 8) {
/* scp3 ports start from 11 */
- port_num = bit + 10;
+ port_num = bit + 11;
sdw_handle_port_interrupt(slave,
port_num,
&port_status[port_num]);
@@ -898,46 +1764,73 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
}
/* 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));
+ if (slave_notify) {
+ if (slave->prop.use_domain_irq && slave->irq)
+ handle_nested_irq(slave->irq);
- slave->ops->interrupt_callback(slave, &slave_intr);
+ 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(slave, SDW_SCP_INT1, clear);
+ ret = sdw_write_no_pm(slave, SDW_SCP_INT1, clear);
if (ret < 0) {
- dev_err(slave->bus->dev,
- "SDW_SCP_INT1 write failed:%d", ret);
- return ret;
+ 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.
*/
- _buf = ret = sdw_read(slave, SDW_SCP_INT1);
+ ret = sdw_read_no_pm(slave, SDW_SCP_INT1);
if (ret < 0) {
- dev_err(slave->bus->dev,
- "SDW_SCP_INT1 read failed:%d", ret);
- return ret;
+ dev_err(&slave->dev,
+ "SDW_SCP_INT1 recheck read failed:%d\n", ret);
+ goto io_err;
}
+ buf = ret;
- ret = sdw_nread(slave, SDW_SCP_INTSTAT2, 2, _buf2);
+ ret = sdw_nread_no_pm(slave, SDW_SCP_INTSTAT2, 2, buf2);
if (ret < 0) {
- dev_err(slave->bus->dev,
- "SDW_SCP_INT2/3 read failed:%d", ret);
- return ret;
+ dev_err(&slave->dev,
+ "SDW_SCP_INT2/3 recheck read failed:%d\n", ret);
+ goto io_err;
}
- /* Make sure no interrupts are pending */
- buf &= _buf;
- buf2[0] &= _buf2[0];
- buf2[1] &= _buf2[1];
- stat = buf || buf2[0] || buf2[1];
+ if (slave->id.class_id) {
+ 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
+ */
+ stat = buf || buf2[0] || buf2[1] || sdca_cascade;
/*
* Exit loop if Slave is continuously in ALERT state even
@@ -949,18 +1842,33 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
} 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");
+ 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)
+ enum sdw_slave_status status)
{
- if ((slave->ops) && (slave->ops->update_status))
- return slave->ops->update_status(slave, status);
+ int ret = 0;
- return 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;
}
/**
@@ -969,16 +1877,58 @@ static int sdw_update_slave_status(struct sdw_slave *slave,
* @status: Status for all Slave(s)
*/
int sdw_handle_slave_status(struct sdw_bus *bus,
- enum sdw_slave_status status[])
+ 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) {
- ret = sdw_program_device_num(bus);
- if (ret)
- dev_err(bus->dev, "Slave attach failed: %d", ret);
+ 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 */
@@ -994,19 +1944,24 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
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)
- dev_err(bus->dev,
- "Slave %d alert handling failed: %d",
+ if (ret < 0)
+ dev_err(&slave->dev,
+ "Slave %d alert handling failed: %d\n",
i, ret);
break;
@@ -1020,27 +1975,120 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
if (prev_status == SDW_SLAVE_ALERT)
break;
+ attached_initializing = true;
+
ret = sdw_initialize_slave(slave);
- if (ret)
- dev_err(bus->dev,
- "Slave %d initialization failed: %d",
+ if (ret < 0)
+ dev_err(&slave->dev,
+ "Slave %d initialization failed: %d\n",
i, ret);
break;
default:
- dev_err(bus->dev, "Invalid slave %d status:%d",
- i, status[i]);
+ dev_err(&slave->dev, "Invalid slave %d status:%d\n",
+ 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);
+ 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);
+
+int sdw_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg)
+{
+ if (msg->len > SDW_BPT_MSG_MAX_BYTES) {
+ dev_err(bus->dev, "Invalid BPT message length %d\n", msg->len);
+ return -EINVAL;
+ }
+
+ /* check device is enumerated */
+ if (slave->dev_num == SDW_ENUM_DEV_NUM ||
+ slave->dev_num > SDW_MAX_DEVICES) {
+ dev_err(&slave->dev, "Invalid device number %d\n", slave->dev_num);
+ return -ENODEV;
+ }
+
+ /* make sure all callbacks are defined */
+ if (!bus->ops->bpt_send_async ||
+ !bus->ops->bpt_wait) {
+ dev_err(bus->dev, "BPT callbacks not defined\n");
+ return -EOPNOTSUPP;
+ }
+
+ return bus->ops->bpt_send_async(bus, slave, msg);
+}
+EXPORT_SYMBOL(sdw_bpt_send_async);
+
+int sdw_bpt_wait(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg)
+{
+ return bus->ops->bpt_wait(bus, slave, msg);
+}
+EXPORT_SYMBOL(sdw_bpt_wait);
+
+int sdw_bpt_send_sync(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg)
+{
+ int ret;
+
+ ret = sdw_bpt_send_async(bus, slave, msg);
+ if (ret < 0)
+ return ret;
+
+ return sdw_bpt_wait(bus, slave, msg);
+}
+EXPORT_SYMBOL(sdw_bpt_send_sync);
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h
index c77de05b8100..02651fbb683a 100644
--- a/drivers/soundwire/bus.h
+++ b/drivers/soundwire/bus.h
@@ -1,10 +1,13 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
-// Copyright(c) 2015-17 Intel Corporation.
+/* 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);
@@ -15,8 +18,30 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus)
}
#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);
+ 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,
@@ -47,10 +72,32 @@ struct sdw_msg {
bool page;
};
+/**
+ * struct sdw_btp_msg - Message structure
+ * @addr: Start Register address accessed in the Slave
+ * @len: number of bytes to transfer. More than 64Kb can be transferred
+ * but a practical limit of SDW_BPT_MSG_MAX_BYTES is enforced.
+ * @dev_num: Slave device number
+ * @flags: transfer flags, indicate if xfer is read or write
+ * @buf: message data buffer (filled by host for write, filled
+ * by Peripheral hardware for reads)
+ */
+struct sdw_bpt_msg {
+ u32 addr;
+ u32 len;
+ u8 dev_num;
+ u8 flags;
+ u8 *buf;
+};
+
#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];
-extern int rows[SDW_FRAME_ROWS];
-extern int 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
@@ -61,6 +108,7 @@ extern int cols[SDW_FRAME_COLS];
* @transport_params: Transport parameters
* @port_params: Port parameters
* @port_node: List node for Master or Slave port_list
+ * @lane: Which lane is used
*
* SoundWire spec has no mention of ports for Master interface but the
* concept is logically extended.
@@ -71,6 +119,7 @@ struct sdw_port_runtime {
struct sdw_transport_params transport_params;
struct sdw_port_params port_params;
struct list_head port_node;
+ unsigned int lane;
};
/**
@@ -115,33 +164,72 @@ struct sdw_master_runtime {
struct list_head bus_node;
};
+struct sdw_transport_data {
+ int hstart;
+ int hstop;
+ int block_offset;
+ int sub_block_offset;
+ unsigned int lane;
+};
+
struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
- enum sdw_data_direction direction,
- unsigned int port_num);
+ enum sdw_data_direction direction,
+ unsigned int port_num);
int sdw_configure_dpn_intr(struct sdw_slave *slave, int port,
- bool enable, int mask);
+ 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);
+int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg);
#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);
+ u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf);
+
+/* 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;
+}
-/* Read-Modify-Write Slave register */
-static inline int
-sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
+/* 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)
{
- int tmp;
+ params->num = port_num;
+ params->bps = bps;
+ params->flow_mode = flow_mode;
+ params->data_mode = data_mode;
+}
- tmp = sdw_read(slave, addr);
- if (tmp < 0)
- return tmp;
+/* broadcast read/write for tests */
+int sdw_bread_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr);
+int sdw_bwrite_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value);
- tmp = (tmp & ~mask) | val;
- return sdw_write(slave, addr, tmp);
-}
+/*
+ * 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);
+void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt,
+ struct sdw_transport_data *t_data);
#endif /* __SDW_BUS_H */
diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c
index 283b2832728e..91e70cb46fb5 100644
--- a/drivers/soundwire/bus_type.c
+++ b/drivers/soundwire/bus_type.c
@@ -6,6 +6,9 @@
#include <linux/pm_domain.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+#include "irq.h"
+#include "sysfs_local.h"
/**
* sdw_get_device_id - find the matching SoundWire device id
@@ -16,39 +19,49 @@
* struct sdw_device_id.
*/
static const struct sdw_device_id *
-sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv)
+sdw_get_device_id(struct sdw_slave *slave, const struct sdw_driver *drv)
{
- const struct sdw_device_id *id = drv->id_table;
+ const struct sdw_device_id *id;
- while (id && id->mfg_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)
+ 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;
- id++;
- }
return NULL;
}
-static int sdw_bus_match(struct device *dev, struct device_driver *ddrv)
+static int sdw_bus_match(struct device *dev, const struct device_driver *ddrv)
{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- struct sdw_driver *drv = drv_to_sdw_driver(ddrv);
+ struct sdw_slave *slave;
+ const struct sdw_driver *drv;
+ int ret = 0;
+
+ if (is_sdw_slave(dev)) {
+ slave = dev_to_sdw_dev(dev);
+ drv = drv_to_sdw_driver(ddrv);
- return !!sdw_get_device_id(slave, drv);
+ 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> */
+ /* modalias is sdw:m<mfg_id>p<part_id>v<version>c<class_id> */
- return snprintf(buf, size, "sdw:m%04Xp%04X\n",
- slave->id.mfg_id, slave->id.part_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);
}
-static int sdw_uevent(struct device *dev, struct kobj_uevent_env *env)
+int sdw_slave_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ const struct sdw_slave *slave = dev_to_sdw_dev(dev);
char modalias[32];
sdw_slave_modalias(slave, modalias, sizeof(modalias));
@@ -59,10 +72,9 @@ static int sdw_uevent(struct device *dev, struct kobj_uevent_env *env)
return 0;
}
-struct bus_type sdw_bus_type = {
+const struct bus_type sdw_bus_type = {
.name = "soundwire",
.match = sdw_bus_match,
- .uevent = sdw_uevent,
};
EXPORT_SYMBOL_GPL(sdw_bus_type);
@@ -73,29 +85,52 @@ static int sdw_drv_probe(struct device *dev)
const struct sdw_device_id *id;
int ret;
+ /*
+ * fw description is mandatory to bind
+ */
+ if (!dev->fwnode)
+ return -ENODEV;
+
+ if (!IS_ENABLED(CONFIG_ACPI) && !dev->of_node)
+ return -ENODEV;
+
id = sdw_get_device_id(slave, drv);
if (!id)
return -ENODEV;
- slave->ops = drv->ops;
-
/*
* attach to power domain but don't turn on (last arg)
*/
- ret = dev_pm_domain_attach(dev, false);
+ ret = dev_pm_domain_attach(dev, 0);
if (ret)
return ret;
+ ret = ida_alloc_max(&slave->bus->slave_ida, SDW_FW_MAX_DEVICES, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(dev, "Failed to allocated ID: %d\n", ret);
+ return ret;
+ }
+ slave->index = ret;
+
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);
+ ida_free(&slave->bus->slave_ida, slave->index);
return ret;
}
+ mutex_lock(&slave->sdw_dev_lock);
+
/* device is probed so let's read the properties now */
- if (slave->ops && slave->ops->read_prop)
- slave->ops->read_prop(slave);
+ if (drv->ops && drv->ops->read_prop)
+ drv->ops->read_prop(slave);
+
+ if (slave->prop.use_domain_irq)
+ sdw_irq_create_mapping(slave);
+
+ /* init the dynamic sysfs attributes we need */
+ ret = sdw_slave_sysfs_dpn_init(slave);
+ if (ret < 0)
+ dev_warn(dev, "failed to initialise sysfs: %d\n", ret);
/*
* Check for valid clk_stop_timeout, use DisCo worst case value of
@@ -107,7 +142,24 @@ static int sdw_drv_probe(struct device *dev)
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->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, "failed to update status at probe: %d\n", ret);
+ }
+
+ mutex_unlock(&slave->sdw_dev_lock);
+
+ dev_dbg(dev, "probe complete\n");
return 0;
}
@@ -118,10 +170,16 @@ static int sdw_drv_remove(struct device *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);
+ ida_free(&slave->bus->slave_ida, slave->index);
return ret;
}
@@ -148,18 +206,15 @@ int __sdw_register_driver(struct sdw_driver *drv, struct module *owner)
if (!drv->probe) {
pr_err("driver %s didn't provide SDW probe routine\n",
- drv->name);
+ drv->driver.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;
+ drv->driver.remove = sdw_drv_remove;
+ drv->driver.shutdown = sdw_drv_shutdown;
+ drv->driver.dev_groups = sdw_attr_groups;
return driver_register(&drv->driver);
}
@@ -177,11 +232,13 @@ 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);
}
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c
index cb6a331f448a..21bb491d026b 100644
--- a/drivers/soundwire/cadence_master.c
+++ b/drivers/soundwire/cadence_master.c
@@ -6,47 +6,65 @@
* Used by Master driver
*/
+#include <linux/cleanup.h>
+#include <linux/crc8.h>
#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"
-#define CDNS_MCP_CONFIG 0x0
+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_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 0x0
#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_IP_MCP_CONFIG 0x0 /* IP offset added at run-time */
+
+#define CDNS_IP_MCP_CONFIG_MCMD_RETRY GENMASK(27, 24)
+#define CDNS_IP_MCP_CONFIG_MPREQ_DELAY GENMASK(20, 16)
+#define CDNS_IP_MCP_CONFIG_MMASTER BIT(7)
+#define CDNS_IP_MCP_CONFIG_SNIFFER BIT(5)
+#define CDNS_IP_MCP_CONFIG_CMD BIT(3)
+#define CDNS_IP_MCP_CONFIG_OP GENMASK(2, 0)
+#define CDNS_IP_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_IP_MCP_CONTROL 0x4 /* IP offset added at run-time */
+
+#define CDNS_IP_MCP_CONTROL_RST_DELAY GENMASK(10, 8)
+#define CDNS_IP_MCP_CONTROL_SW_RST BIT(5)
+#define CDNS_IP_MCP_CONTROL_CLK_PAUSE BIT(3)
+#define CDNS_IP_MCP_CONTROL_CMD_ACCEPT BIT(1)
+#define CDNS_IP_MCP_CONTROL_BLOCK_WAKEUP BIT(0)
+
+#define CDNS_IP_MCP_CMDCTRL 0x8 /* IP offset added at run-time */
+
+#define CDNS_IP_MCP_CMDCTRL_INSERT_PARITY_ERR BIT(2)
-#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_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)
@@ -56,6 +74,7 @@
#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
@@ -66,6 +85,7 @@
#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)
@@ -75,14 +95,19 @@
#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_SDW_SLAVE_STAT 0x50
-#define CDNS_MCP_SLAVE_STAT_MASK BIT(1, 0)
+#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
@@ -96,8 +121,8 @@
#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_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
@@ -106,16 +131,16 @@
#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_IP_MCP_CMD_BASE 0x80 /* IP offset added at run-time */
+#define CDNS_IP_MCP_RESP_BASE 0x80 /* IP offset added at run-time */
+/* 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_H GENMASK(23, 16)
-#define CDNS_MCP_CMD_REG_ADDR_L GENMASK(15, 8)
+#define CDNS_MCP_CMD_REG_ADDR GENMASK(23, 8)
#define CDNS_MCP_CMD_REG_DATA GENMASK(7, 0)
#define CDNS_MCP_CMD_READ 2
@@ -157,8 +182,10 @@
#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_PORTCTRL_BULK_ENABLE BIT(16)
#define CDNS_PORT_OFFSET 0x80
@@ -169,14 +196,7 @@
#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_TX_TIMEOUT 500
#define CDNS_SCP_RX_FIFOLEVEL 0x2
@@ -193,6 +213,16 @@ static inline void cdns_writel(struct sdw_cdns *cdns, int offset, u32 value)
writel(value, cdns->registers + offset);
}
+static inline u32 cdns_ip_readl(struct sdw_cdns *cdns, int offset)
+{
+ return cdns_readl(cdns, cdns->ip_offset + offset);
+}
+
+static inline void cdns_ip_writel(struct sdw_cdns *cdns, int offset, u32 value)
+{
+ return cdns_writel(cdns, cdns->ip_offset + offset, value);
+}
+
static inline void cdns_updatel(struct sdw_cdns *cdns,
int offset, u32 mask, u32 val)
{
@@ -203,32 +233,337 @@ static inline void cdns_updatel(struct sdw_cdns *cdns,
cdns_writel(cdns, offset, tmp);
}
-static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value)
+static inline void cdns_ip_updatel(struct sdw_cdns *cdns,
+ int offset, u32 mask, u32 val)
+{
+ cdns_updatel(cdns, cdns->ip_offset + offset, mask, val);
+}
+
+static int cdns_set_wait(struct sdw_cdns *cdns, int offset, u32 mask, u32 value)
{
int timeout = 10;
u32 reg_read;
- writel(value, cdns->registers + offset);
-
- /* Wait for bit to be self cleared */
+ /* Wait for bit to be set */
do {
reg_read = readl(cdns->registers + offset);
- if ((reg_read & value) == 0)
+ if ((reg_read & mask) == value)
return 0;
timeout--;
- udelay(50);
+ usleep_range(50, 100);
} while (timeout != 0);
- return -EAGAIN;
+ 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;
+}
+
+/**
+ * sdw_cdns_config_update() - Update configurations
+ * @cdns: Cadence instance
+ */
+void sdw_cdns_config_update(struct sdw_cdns *cdns)
+{
+ /* commit changes */
+ cdns_writel(cdns, CDNS_MCP_CONFIG_UPDATE, CDNS_MCP_CONFIG_UPDATE_BIT);
+}
+EXPORT_SYMBOL(sdw_cdns_config_update);
+
+/**
+ * sdw_cdns_config_update_set_wait() - wait until configuration update bit is self-cleared
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_config_update_set_wait(struct sdw_cdns *cdns)
+{
+ /* the hardware recommendation is to wait at least 300us */
+ return cdns_set_wait(cdns, CDNS_MCP_CONFIG_UPDATE,
+ CDNS_MCP_CONFIG_UPDATE_BIT, 0);
+}
+EXPORT_SYMBOL(sdw_cdns_config_update_set_wait);
+
+/*
+ * 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;
+ ssize_t ret;
+ int num_ports;
+ int i, j;
+
+ char *buf __free(kfree) = 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);
+
+ 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_ip_updatel(cdns, CDNS_IP_MCP_CMDCTRL,
+ CDNS_IP_MCP_CMDCTRL_INSERT_PARITY_ERR,
+ CDNS_IP_MCP_CMDCTRL_INSERT_PARITY_ERR);
+
+ /* commit changes */
+ ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE, CDNS_MCP_CONFIG_UPDATE_BIT);
+ if (ret < 0)
+ goto unlock;
+
+ /* 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_ip_updatel(cdns, CDNS_IP_MCP_CMDCTRL,
+ CDNS_IP_MCP_CMDCTRL_INSERT_PARITY_ERR,
+ 0);
+
+ /* commit changes */
+ ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE, CDNS_MCP_CONFIG_UPDATE_BIT);
+ if (ret < 0)
+ goto unlock;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+unlock:
+ /* Continue bus operation with parity error injection disabled */
+ mutex_unlock(&bus->bus_lock);
+
+ /*
+ * 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)
+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;
@@ -237,33 +572,60 @@ static enum sdw_command_response cdns_fill_msg_resp(
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");
- }
+ 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(cdns->dev, "Msg NACKed for Slave %d\n", msg->dev_num);
+ dev_err_ratelimited(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);
+ }
+
+ if (no_ack) {
+ dev_dbg_ratelimited(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);
+ 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_IP_MCP_CMD_BASE;
+
+ for (i = 0; i < num_resp; i++) {
+ cdns->response_buf[i] = cdns_ip_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)
+ int offset, int count, bool defer)
{
unsigned long time;
u32 base, i, data;
@@ -275,19 +637,20 @@ _cdns_xfer_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int cmd,
cdns->msg_count = count;
}
- base = CDNS_MCP_CMD_BASE;
- addr = msg->addr;
+ base = CDNS_IP_MCP_CMD_BASE;
+ addr = msg->addr + offset;
for (i = 0; i < count; i++) {
- data = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR);
- data |= cmd << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND);
- data |= addr++ << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
+ data = FIELD_PREP(CDNS_MCP_CMD_DEV_ADDR, msg->dev_num);
+ data |= FIELD_PREP(CDNS_MCP_CMD_COMMAND, cmd);
+ data |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, addr);
+ addr++;
if (msg->flags == SDW_MSG_FLAG_WRITE)
data |= msg->buf[i + offset];
- data |= msg->ssp_sync << SDW_REG_SHIFT(CDNS_MCP_CMD_SSP_TAG);
- cdns_writel(cdns, base, data);
+ data |= FIELD_PREP(CDNS_MCP_CMD_SSP_TAG, msg->ssp_sync);
+ cdns_ip_writel(cdns, base, data);
base += CDNS_MCP_CMD_WORD_LEN;
}
@@ -296,18 +659,23 @@ _cdns_xfer_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int cmd,
/* wait for timeout or response */
time = wait_for_completion_timeout(&cdns->tx_complete,
- msecs_to_jiffies(CDNS_TX_TIMEOUT));
+ msecs_to_jiffies(CDNS_TX_TIMEOUT));
if (!time) {
- dev_err(cdns->dev, "IO transfer timed out\n");
+ 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)
+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;
@@ -320,23 +688,23 @@ static enum sdw_command_response cdns_program_scp_addr(
cdns->msg_count = CDNS_SCP_RX_FIFOLEVEL;
}
- data[0] = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR);
- data[0] |= 0x3 << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND);
+ data[0] = FIELD_PREP(CDNS_MCP_CMD_DEV_ADDR, msg->dev_num);
+ data[0] |= FIELD_PREP(CDNS_MCP_CMD_COMMAND, 0x3);
data[1] = data[0];
- data[0] |= SDW_SCP_ADDRPAGE1 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
- data[1] |= SDW_SCP_ADDRPAGE2 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L);
+ data[0] |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, SDW_SCP_ADDRPAGE1);
+ data[1] |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, SDW_SCP_ADDRPAGE2);
data[0] |= msg->addr_page1;
data[1] |= msg->addr_page2;
- base = CDNS_MCP_CMD_BASE;
- cdns_writel(cdns, base, data[0]);
+ base = CDNS_IP_MCP_CMD_BASE;
+ cdns_ip_writel(cdns, base, data[0]);
base += CDNS_MCP_CMD_WORD_LEN;
- cdns_writel(cdns, base, data[1]);
+ cdns_ip_writel(cdns, base, data[1]);
time = wait_for_completion_timeout(&cdns->tx_complete,
- msecs_to_jiffies(CDNS_TX_TIMEOUT));
+ msecs_to_jiffies(CDNS_TX_TIMEOUT));
if (!time) {
dev_err(cdns->dev, "SCP Msg trf timed out\n");
msg->len = 0;
@@ -347,22 +715,24 @@ static enum sdw_command_response cdns_program_scp_addr(
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");
+ 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");
+ 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(cdns->dev,
- "SCP_addrpage NACKed for Slave %d", msg->dev_num);
+ dev_err_ratelimited(cdns->dev,
+ "SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- } else if (no_ack) {
- dev_dbg(cdns->dev,
- "SCP_addrpage ignored for Slave %d", msg->dev_num);
+ }
+
+ if (no_ack) {
+ dev_dbg_ratelimited(cdns->dev,
+ "SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
}
@@ -410,27 +780,25 @@ cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg)
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;
+ CDNS_MCP_CMD_LEN, false);
+ if (ret != SDW_CMD_OK)
+ return ret;
}
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);
+ return SDW_CMD_OK;
-exit:
- return ret;
+ 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)
+cdns_xfer_msg_defer(struct sdw_bus *bus)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_defer *defer = &bus->defer_msg;
+ struct sdw_msg *msg = defer->msg;
int cmd = 0, ret;
/* for defer only 1 message is supported */
@@ -441,102 +809,97 @@ cdns_xfer_msg_defer(struct sdw_bus *bus,
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)
+u32 cdns_read_ping_status(struct sdw_bus *bus)
{
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);
+ return cdns_readl(cdns, CDNS_MCP_SLAVE_STAT);
}
-EXPORT_SYMBOL(cdns_reset_page_addr);
+EXPORT_SYMBOL(cdns_read_ping_status);
/*
* 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)
+ u64 slave_intstat)
{
enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
bool is_slave = false;
- u64 slave, mask;
+ u32 mask;
+ u32 val;
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;
+ mask = (slave_intstat >> (i * CDNS_MCP_SLAVE_STATUS_NUM)) &
+ CDNS_MCP_SLAVE_STATUS_BITS;
- is_slave = true;
set_status = 0;
- if (mask & CDNS_MCP_SLAVE_INTSTAT_RESERVED) {
- status[i] = SDW_SLAVE_RESERVED;
- set_status++;
- }
+ if (mask) {
+ is_slave = true;
- if (mask & CDNS_MCP_SLAVE_INTSTAT_ATTACHED) {
- status[i] = SDW_SLAVE_ATTACHED;
- set_status++;
- }
+ if (mask & CDNS_MCP_SLAVE_INTSTAT_RESERVED) {
+ status[i] = SDW_SLAVE_RESERVED;
+ set_status++;
+ }
- if (mask & CDNS_MCP_SLAVE_INTSTAT_ALERT) {
- status[i] = SDW_SLAVE_ALERT;
- 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++;
+ 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
- */
+ /*
+ * 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);
+ if (is_slave) {
+ int ret;
+
+ mutex_lock(&cdns->status_update_lock);
+ ret = sdw_handle_slave_status(&cdns->bus, status);
+ mutex_unlock(&cdns->status_update_lock);
+ return ret;
+ }
return 0;
}
@@ -550,7 +913,6 @@ 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)
@@ -558,26 +920,36 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id)
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) {
+ struct sdw_bus *bus = &cdns->bus;
+ struct sdw_defer *defer = &bus->defer_msg;
+
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
+ if (defer && defer->msg) {
+ cdns_fill_msg_resp(cdns, defer->msg,
+ defer->length, 0);
+ complete(&defer->complete);
+ } else {
complete(&cdns->tx_complete);
+ }
}
- if (int_status & CDNS_MCP_INT_CTRL_CLASH) {
+ 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");
- int_status |= CDNS_MCP_INT_CTRL_CLASH;
}
if (int_status & CDNS_MCP_INT_DATA_CLASH) {
@@ -586,94 +958,317 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id)
* 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 (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);
+ CDNS_MCP_INT_SLAVE_MASK, 0);
int_status &= ~CDNS_MCP_INT_SLAVE_MASK;
- ret = IRQ_WAKE_THREAD;
+
+ /*
+ * 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 ret;
+ return IRQ_HANDLED;
}
EXPORT_SYMBOL(sdw_cdns_irq);
+static void cdns_check_attached_status_dwork(struct work_struct *work)
+{
+ struct sdw_cdns *cdns =
+ container_of(work, struct sdw_cdns, attach_dwork.work);
+ enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
+ u32 val;
+ int ret;
+ int i;
+
+ val = cdns_readl(cdns, CDNS_MCP_SLAVE_STAT);
+
+ for (i = 0; i <= SDW_MAX_DEVICES; i++) {
+ status[i] = val & 0x3;
+ if (status[i])
+ dev_dbg(cdns->dev, "Peripheral %d status: %d\n", i, status[i]);
+ val >>= 2;
+ }
+
+ mutex_lock(&cdns->status_update_lock);
+ ret = sdw_handle_slave_status(&cdns->bus, status);
+ mutex_unlock(&cdns->status_update_lock);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: sdw_handle_slave_status failed: %d\n", __func__, ret);
+}
+
/**
- * sdw_cdns_thread() - Cadence irq thread handler
- * @irq: irq number
- * @dev_id: irq context
+ * 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
*/
-irqreturn_t sdw_cdns_thread(int irq, void *dev_id)
+static void cdns_update_slave_status_work(struct work_struct *work)
{
- struct sdw_cdns *cdns = dev_id;
+ struct sdw_cdns *cdns =
+ container_of(work, struct sdw_cdns, work);
u32 slave0, slave1;
+ u64 slave_intstat;
+ u32 device0_status;
+ int retry_count = 0;
- dev_dbg(cdns->dev, "Slave status change\n");
+ /*
+ * 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);
- cdns_update_slave_status(cdns, slave0, slave1);
+ /*
+ * 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);
- /* clear and unmask Slave interrupt now */
- cdns_writel(cdns, CDNS_MCP_INTSTAT, CDNS_MCP_INT_SLAVE_MASK);
+ /* 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);
+ 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 ip_mcp_control;
+ u32 mcp_control;
+ u32 mcp_config_update;
+ int i;
+
+ if (initial_delay)
+ usleep_range(1000, 1500);
+
+ ip_mcp_control = cdns_ip_readl(cdns, CDNS_IP_MCP_CONTROL);
+
+ /* the following bits should be cleared immediately */
+ if (ip_mcp_control & CDNS_IP_MCP_CONTROL_SW_RST)
+ dev_err(cdns->dev, "%s failed: IP_MCP_CONTROL_SW_RST is not cleared\n", string);
+
+ 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_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);
+ }
- return IRQ_HANDLED;
}
-EXPORT_SYMBOL(sdw_cdns_thread);
+EXPORT_SYMBOL(sdw_cdns_check_self_clearing_bits);
/*
* init routines
*/
-static int _cdns_enable_interrupt(struct sdw_cdns *cdns)
+
+/**
+ * sdw_cdns_exit_reset() - Program reset parameters and start bus operations
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_exit_reset(struct sdw_cdns *cdns)
{
- u32 mask;
+ /* keep reset delay unchanged to 4096 cycles */
- cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK0,
- CDNS_MCP_SLAVE_INTMASK0_MASK);
- cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK1,
- CDNS_MCP_SLAVE_INTMASK1_MASK);
+ /* use hardware generated reset */
+ cdns_updatel(cdns, CDNS_MCP_CONTROL,
+ CDNS_MCP_CONTROL_HW_RST,
+ CDNS_MCP_CONTROL_HW_RST);
- 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;
+ /* commit changes */
+ return cdns_config_update(cdns);
+}
+EXPORT_SYMBOL(sdw_cdns_exit_reset);
- cdns_writel(cdns, CDNS_MCP_INTMASK, mask);
+/**
+ * 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;
- return 0;
+ 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 and update config
+ * 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)
+int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state)
{
- int ret;
+ u32 slave_intmask0 = 0;
+ u32 slave_intmask1 = 0;
+ u32 mask = 0;
- _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");
+ if (!state)
+ goto update_masks;
- return ret;
+ 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 **stream,
+ u32 num)
{
struct sdw_cdns_pdi *pdi;
int i;
@@ -686,8 +1281,7 @@ static int cdns_allocate_pdi(struct sdw_cdns *cdns,
return -ENOMEM;
for (i = 0; i < num; i++) {
- pdi[i].num = i + pdi_offset;
- pdi[i].assigned = false;
+ pdi[i].num = i;
}
*stream = pdi;
@@ -701,41 +1295,28 @@ static int cdns_allocate_pdi(struct sdw_cdns *cdns,
* @config: Stream configurations
*/
int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
- struct sdw_cdns_stream_config config)
+ struct sdw_cdns_stream_config config)
{
struct sdw_cdns_streams *stream;
- int offset, i, ret;
+ int ret;
cdns->pcm.num_bd = config.pcm_bd;
cdns->pcm.num_in = config.pcm_in;
cdns->pcm.num_out = config.pcm_out;
- cdns->pdm.num_bd = config.pdm_bd;
- cdns->pdm.num_in = config.pdm_in;
- cdns->pdm.num_out = config.pdm_out;
/* Allocate PDIs for PCMs */
stream = &cdns->pcm;
- /* First two PDIs are reserved for bulk transfers */
- stream->num_bd -= CDNS_PCM_PDI_OFFSET;
- offset = CDNS_PCM_PDI_OFFSET;
-
- ret = cdns_allocate_pdi(cdns, &stream->bd,
- stream->num_bd, offset);
+ /* we allocate PDI0 and PDI1 which are used for Bulk */
+ ret = cdns_allocate_pdi(cdns, &stream->bd, stream->num_bd);
if (ret)
return ret;
- offset += stream->num_bd;
-
- ret = cdns_allocate_pdi(cdns, &stream->in,
- stream->num_in, offset);
+ ret = cdns_allocate_pdi(cdns, &stream->in, stream->num_in);
if (ret)
return ret;
- offset += stream->num_in;
-
- ret = cdns_allocate_pdi(cdns, &stream->out,
- stream->num_out, offset);
+ ret = cdns_allocate_pdi(cdns, &stream->out, stream->num_out);
if (ret)
return ret;
@@ -743,170 +1324,258 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out;
cdns->num_ports = stream->num_pdi;
- /* Allocate PDIs for PDMs */
- stream = &cdns->pdm;
- offset = CDNS_PDM_PDI_OFFSET;
- ret = cdns_allocate_pdi(cdns, &stream->bd,
- stream->num_bd, offset);
- if (ret)
- return ret;
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_pdi_init);
- offset += stream->num_bd;
+static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols)
+{
+ u32 val;
+ int c;
+ int r;
- ret = cdns_allocate_pdi(cdns, &stream->in,
- stream->num_in, offset);
- if (ret)
- return ret;
+ r = sdw_find_row_index(n_rows);
+ c = sdw_find_col_index(n_cols);
- offset += stream->num_in;
+ val = FIELD_PREP(CDNS_MCP_FRAME_SHAPE_ROW_MASK, r);
+ val |= FIELD_PREP(CDNS_MCP_FRAME_SHAPE_COL_MASK, c);
- ret = cdns_allocate_pdi(cdns, &stream->out,
- stream->num_out, offset);
- if (ret)
- return ret;
+ return val;
+}
- /* Update total number of PDM PDIs */
- stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out;
- cdns->num_ports += stream->num_pdi;
+static int 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;
- cdns->ports = devm_kcalloc(cdns->dev, cdns->num_ports,
- sizeof(*cdns->ports), GFP_KERNEL);
- if (!cdns->ports) {
- ret = -ENOMEM;
- return ret;
- }
+ dev_dbg(cdns->dev, "mclk %d max %d row %d col %d\n",
+ prop->mclk_freq,
+ prop->max_clk_freq,
+ prop->default_row,
+ prop->default_col);
- for (i = 0; i < cdns->num_ports; i++) {
- cdns->ports[i].assigned = false;
- cdns->ports[i].num = i + 1; /* Port 0 reserved for bulk */
+ if (!prop->default_frame_rate || !prop->default_row) {
+ dev_err(cdns->dev, "Default frame_rate %d or row %d is invalid\n",
+ prop->default_frame_rate, prop->default_row);
+ return -EINVAL;
}
+ /* Set clock divider */
+ divider = (prop->mclk_freq * SDW_DOUBLE_RATE_FACTOR /
+ bus->params.curr_dr_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);
+
+ /* Set frame shape base on the actual bus frequency. */
+ prop->default_col = bus->params.curr_dr_freq /
+ prop->default_frame_rate / prop->default_row;
+
+ /*
+ * 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);
+
return 0;
}
-EXPORT_SYMBOL(sdw_cdns_pdi_init);
/**
- * sdw_cdns_init() - Cadence initialization
+ * sdw_cdns_soft_reset() - Cadence soft-reset
* @cdns: Cadence instance
*/
-int sdw_cdns_init(struct sdw_cdns *cdns)
+int sdw_cdns_soft_reset(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);
+ cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_SOFT_RST,
+ CDNS_MCP_CONTROL_SOFT_RST);
+
+ ret = cdns_config_update(cdns);
if (ret < 0) {
- dev_err(cdns->dev, "Couldn't exit from clock stop\n");
+ dev_err(cdns->dev, "%s: config update failed\n", __func__);
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);
+ ret = cdns_set_wait(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_SOFT_RST, 0);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: Soft Reset timed out\n", __func__);
- /* Set the default frame shape */
- cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, CDNS_DEFAULT_FRAME_SHAPE);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_cdns_soft_reset);
- /* 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);
+/**
+ * sdw_cdns_init() - Cadence initialization
+ * @cdns: Cadence instance
+ */
+int sdw_cdns_init(struct sdw_cdns *cdns)
+{
+ int ret;
+ u32 val;
+
+ ret = cdns_init_clock_ctrl(cdns);
+ if (ret)
+ return ret;
+
+ 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);
+ cdns_ip_updatel(cdns, CDNS_IP_MCP_CONTROL, CDNS_IP_MCP_CONTROL_CMD_ACCEPT,
+ CDNS_IP_MCP_CONTROL_CMD_ACCEPT);
+
+ /* disable wakeup */
+ cdns_ip_updatel(cdns, CDNS_IP_MCP_CONTROL,
+ CDNS_IP_MCP_CONTROL_BLOCK_WAKEUP,
+ 0);
/* 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;
+ cdns_writel(cdns, CDNS_MCP_CONFIG, val);
+
+ /* Configure IP mcp config */
+ val = cdns_ip_readl(cdns, CDNS_IP_MCP_CONFIG);
+
+ /* enable bus operations with clock and data */
+ val &= ~CDNS_IP_MCP_CONFIG_OP;
+ val |= CDNS_IP_MCP_CONFIG_OP_NORMAL;
/* Set cmd mode for Tx and Rx cmds */
- val &= ~CDNS_MCP_CONFIG_CMD;
+ val &= ~CDNS_IP_MCP_CONFIG_CMD;
- /* Set operation to normal */
- val &= ~CDNS_MCP_CONFIG_OP;
- val |= CDNS_MCP_CONFIG_OP_NORMAL;
+ /* Disable sniffer mode */
+ val &= ~CDNS_IP_MCP_CONFIG_SNIFFER;
- cdns_writel(cdns, CDNS_MCP_CONFIG, val);
+ if (cdns->bus.multi_link)
+ /* Set Multi-master mode to take gsync into account */
+ val |= CDNS_IP_MCP_CONFIG_MMASTER;
+
+ /* leave frame delay to hardware default of 0x1F */
+
+ /* leave command retry to hardware default of 0 */
+
+ cdns_ip_writel(cdns, CDNS_IP_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, mcp_clkctrl;
+ int mcp_clkctrl_off;
int divider;
if (!params->curr_dr_freq) {
- dev_err(cdns->dev, "NULL curr_dr_freq");
+ dev_err(cdns->dev, "NULL curr_dr_freq\n");
return -EINVAL;
}
- divider = (params->max_dr_freq / params->curr_dr_freq) - 1;
+ 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;
- mcp_clkctrl = cdns_readl(cdns, mcp_clkctrl_off);
- mcp_clkctrl |= divider;
- cdns_writel(cdns, mcp_clkctrl_off, mcp_clkctrl);
+ 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_port_params *p_params, unsigned int bank)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
- int dpn_config = 0, dpn_config_off;
+ 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 = CDNS_DPN_B1_CONFIG(p_params->num);
- else
- dpn_config_off = CDNS_DPN_B0_CONFIG(p_params->num);
+ 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);
+ dpn_config = cdns_readl(cdns, dpn_config_off_source);
- dpn_config |= ((p_params->bps - 1) <<
- SDW_REG_SHIFT(CDNS_DPN_CONFIG_WL));
- dpn_config |= (p_params->flow_mode <<
- SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_FLOW));
- dpn_config |= (p_params->data_mode <<
- SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_DAT));
+ /* 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, dpn_config);
+ 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_transport_params *t_params,
+ enum sdw_reg_bank bank)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
- int dpn_offsetctrl = 0, dpn_offsetctrl_off;
- int dpn_config = 0, dpn_config_off;
- int dpn_hctrl = 0, dpn_hctrl_off;
- int num = t_params->port_num;
- int dpn_samplectrl_off;
+ 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
@@ -914,45 +1583,65 @@ static int cdns_transport_params(struct sdw_bus *bus,
*/
if (bank) {
- dpn_config_off = CDNS_DPN_B1_CONFIG(num);
- dpn_samplectrl_off = CDNS_DPN_B1_SAMPLE_CTRL(num);
- dpn_hctrl_off = CDNS_DPN_B1_HCTRL(num);
- dpn_offsetctrl_off = CDNS_DPN_B1_OFFSET_CTRL(num);
+ 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 = CDNS_DPN_B0_CONFIG(num);
- dpn_samplectrl_off = CDNS_DPN_B0_SAMPLE_CTRL(num);
- dpn_hctrl_off = CDNS_DPN_B0_HCTRL(num);
- dpn_offsetctrl_off = CDNS_DPN_B0_OFFSET_CTRL(num);
+ 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);
-
- dpn_config |= (t_params->blk_grp_ctrl <<
- SDW_REG_SHIFT(CDNS_DPN_CONFIG_BGC));
- dpn_config |= (t_params->blk_pkg_mode <<
- SDW_REG_SHIFT(CDNS_DPN_CONFIG_BPM));
- cdns_writel(cdns, dpn_config_off, dpn_config);
+ 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);
- dpn_offsetctrl |= (t_params->offset1 <<
- SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_1));
- dpn_offsetctrl |= (t_params->offset2 <<
- SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_2));
- cdns_writel(cdns, dpn_offsetctrl_off, dpn_offsetctrl);
+ 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);
- dpn_hctrl |= (t_params->hstart <<
- SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTART));
- dpn_hctrl |= (t_params->hstop << SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTOP));
- dpn_hctrl |= (t_params->lane_ctrl <<
- SDW_REG_SHIFT(CDNS_DPN_HCTRL_LCTRL));
+ 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);
- cdns_writel(cdns, dpn_hctrl_off, dpn_hctrl);
- cdns_writel(cdns, dpn_samplectrl_off, (t_params->sample_interval - 1));
+ 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_enable_ch *enable_ch, unsigned int bank)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
int dpn_chnen_off, ch_mask;
@@ -975,6 +1664,153 @@ static const struct sdw_master_port_ops cdns_port_ops = {
};
/**
+ * 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_ip_updatel(cdns, CDNS_IP_MCP_CONTROL,
+ CDNS_IP_MCP_CONTROL_BLOCK_WAKEUP,
+ CDNS_IP_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_ip_updatel(cdns, CDNS_IP_MCP_CONTROL,
+ CDNS_IP_MCP_CONTROL_BLOCK_WAKEUP, 0);
+
+ cdns_ip_updatel(cdns, CDNS_IP_MCP_CONTROL, CDNS_IP_MCP_CONTROL_CMD_ACCEPT,
+ CDNS_IP_MCP_CONTROL_CMD_ACCEPT);
+
+ if (!bus_reset) {
+
+ /* enable bus operations with clock and data */
+ cdns_ip_updatel(cdns, CDNS_IP_MCP_CONFIG,
+ CDNS_IP_MCP_CONFIG_OP,
+ CDNS_IP_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
*/
@@ -983,35 +1819,59 @@ int sdw_cdns_probe(struct sdw_cdns *cdns)
init_completion(&cdns->tx_complete);
cdns->bus.port_ops = &cdns_port_ops;
+ mutex_init(&cdns->status_update_lock);
+
+ INIT_WORK(&cdns->work, cdns_update_slave_status_work);
+ INIT_DELAYED_WORK(&cdns->attach_dwork, cdns_check_attached_status_dwork);
+
return 0;
}
EXPORT_SYMBOL(sdw_cdns_probe);
int cdns_set_sdw_stream(struct snd_soc_dai *dai,
- void *stream, bool pcm, int direction)
+ void *stream, int direction)
{
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
- struct sdw_cdns_dma_data *dma;
+ struct sdw_cdns_dai_runtime *dai_runtime;
- dma = kzalloc(sizeof(*dma), GFP_KERNEL);
- if (!dma)
- return -ENOMEM;
+ dai_runtime = cdns->dai_runtime_array[dai->id];
- if (pcm)
- dma->stream_type = SDW_STREAM_PCM;
- else
- dma->stream_type = SDW_STREAM_PDM;
+ if (stream) {
+ /* first paranoia check */
+ if (dai_runtime) {
+ dev_err(dai->dev,
+ "dai_runtime already allocated for dai %s\n",
+ dai->name);
+ return -EINVAL;
+ }
- dma->bus = &cdns->bus;
- dma->link_id = cdns->instance;
+ /* allocate and set dai_runtime info */
+ dai_runtime = kzalloc(sizeof(*dai_runtime), GFP_KERNEL);
+ if (!dai_runtime)
+ return -ENOMEM;
- dma->stream = stream;
+ dai_runtime->stream_type = SDW_STREAM_PCM;
- if (direction == SNDRV_PCM_STREAM_PLAYBACK)
- dai->playback_dma_data = dma;
- else
- dai->capture_dma_data = dma;
+ dai_runtime->bus = &cdns->bus;
+ dai_runtime->link_id = cdns->instance;
+
+ dai_runtime->stream = stream;
+ dai_runtime->direction = direction;
+
+ cdns->dai_runtime_array[dai->id] = dai_runtime;
+ } else {
+ /* second paranoia check */
+ if (!dai_runtime) {
+ dev_err(dai->dev,
+ "dai_runtime not allocated for dai %s\n",
+ dai->name);
+ return -EINVAL;
+ }
+ /* for NULL stream we release allocated dai_runtime */
+ kfree(dai_runtime);
+ cdns->dai_runtime_array[dai->id] = NULL;
+ }
return 0;
}
EXPORT_SYMBOL(cdns_set_sdw_stream);
@@ -1022,20 +1882,21 @@ EXPORT_SYMBOL(cdns_set_sdw_stream);
* @cdns: Cadence instance
* @num: Number of PDIs
* @pdi: PDI instances
+ * @dai_id: DAI id
*
- * Find and return a free PDI for a given PDI array
+ * 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 num, struct sdw_cdns_pdi *pdi)
+ unsigned int num,
+ struct sdw_cdns_pdi *pdi,
+ int dai_id)
{
int i;
- for (i = 0; i < num; i++) {
- if (pdi[i].assigned == true)
- continue;
- pdi[i].assigned = true;
- return &pdi[i];
- }
+ for (i = 0; i < num; i++)
+ if (pdi[i].num == dai_id)
+ return &pdi[i];
return NULL;
}
@@ -1044,141 +1905,710 @@ static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns,
* sdw_cdns_config_stream: Configure a stream
*
* @cdns: Cadence instance
- * @port: Cadence data port
* @ch: Channel count
* @dir: Data direction
* @pdi: PDI to be used
*/
void sdw_cdns_config_stream(struct sdw_cdns *cdns,
- struct sdw_cdns_port *port,
- u32 ch, u32 dir, struct sdw_cdns_pdi *pdi)
+ u32 ch, u32 dir, struct sdw_cdns_pdi *pdi)
{
u32 offset, val = 0;
- if (dir == SDW_DATA_DIR_RX)
+ if (dir == SDW_DATA_DIR_RX) {
val = CDNS_PORTCTRL_DIRN;
- offset = CDNS_PORTCTRL + port->num * CDNS_PORT_OFFSET;
- cdns_updatel(cdns, offset, CDNS_PORTCTRL_DIRN, val);
-
- val = port->num;
- val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL);
+ if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL)
+ val |= CDNS_PORTCTRL_TEST_FAILED;
+ } else if (pdi->num == 0 || pdi->num == 1) {
+ val |= CDNS_PORTCTRL_BULK_ENABLE;
+ }
+ offset = CDNS_PORTCTRL + pdi->num * CDNS_PORT_OFFSET;
+ cdns_updatel(cdns, offset,
+ CDNS_PORTCTRL_DIRN | CDNS_PORTCTRL_TEST_FAILED |
+ CDNS_PORTCTRL_BULK_ENABLE,
+ val);
+
+ /* The DataPort0 needs to be mapped to both PDI0 and PDI1 ! */
+ if (pdi->num == 1)
+ val = 0;
+ else
+ 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);
/**
- * cdns_get_num_pdi() - Get number of PDIs required
+ * sdw_cdns_alloc_pdi() - Allocate a PDI
*
* @cdns: Cadence instance
- * @pdi: PDI to be used
- * @num: Number of PDIs
- * @ch_count: Channel count
+ * @stream: Stream to be allocated
+ * @ch: Channel count
+ * @dir: Data direction
+ * @dai_id: DAI id
*/
-static int cdns_get_num_pdi(struct sdw_cdns *cdns,
- struct sdw_cdns_pdi *pdi,
- unsigned int num, u32 ch_count)
+struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns,
+ struct sdw_cdns_streams *stream,
+ u32 ch, u32 dir, int dai_id)
{
- int i, pdis = 0;
+ struct sdw_cdns_pdi *pdi = NULL;
- for (i = 0; i < num; i++) {
- if (pdi[i].assigned == true)
- continue;
+ if (dir == SDW_DATA_DIR_RX)
+ pdi = cdns_find_pdi(cdns, stream->num_in, stream->in,
+ dai_id);
+ else
+ pdi = cdns_find_pdi(cdns, stream->num_out, stream->out,
+ dai_id);
- if (pdi[i].ch_count < ch_count)
- ch_count -= pdi[i].ch_count;
- else
- ch_count = 0;
+ /* check if we found a PDI, else find in bi-directional */
+ if (!pdi)
+ pdi = cdns_find_pdi(cdns, 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;
+ }
- pdis++;
+ return pdi;
+}
+EXPORT_SYMBOL(sdw_cdns_alloc_pdi);
- if (!ch_count)
- break;
- }
+/*
+ * the MIPI SoundWire CRC8 polynomial is X^8 + X^6 + X^3 + X^2 + 1, MSB first
+ * The value is (1)01001101 = 0x4D
+ *
+ * the table below was generated with
+ *
+ * u8 crc8_lookup_table[CRC8_TABLE_SIZE];
+ * crc8_populate_msb(crc8_lookup_table, SDW_CRC8_POLY);
+ *
+ */
+#define SDW_CRC8_SEED 0xFF
+#define SDW_CRC8_POLY 0x4D
+
+static const u8 sdw_crc8_lookup_msb[CRC8_TABLE_SIZE] = {
+ 0x00, 0x4d, 0x9a, 0xd7, 0x79, 0x34, 0xe3, 0xae, /* 0 - 7 */
+ 0xf2, 0xbf, 0x68, 0x25, 0x8b, 0xc6, 0x11, 0x5c, /* 8 -15 */
+ 0xa9, 0xe4, 0x33, 0x7e, 0xd0, 0x9d, 0x4a, 0x07, /* 16 - 23 */
+ 0x5b, 0x16, 0xc1, 0x8c, 0x22, 0x6f, 0xb8, 0xf5, /* 24 - 31 */
+ 0x1f, 0x52, 0x85, 0xc8, 0x66, 0x2b, 0xfc, 0xb1, /* 32 - 39 */
+ 0xed, 0xa0, 0x77, 0x3a, 0x94, 0xd9, 0x0e, 0x43, /* 40 - 47 */
+ 0xb6, 0xfb, 0x2c, 0x61, 0xcf, 0x82, 0x55, 0x18, /* 48 - 55 */
+ 0x44, 0x09, 0xde, 0x93, 0x3d, 0x70, 0xa7, 0xea, /* 56 - 63 */
+ 0x3e, 0x73, 0xa4, 0xe9, 0x47, 0x0a, 0xdd, 0x90, /* 64 - 71 */
+ 0xcc, 0x81, 0x56, 0x1b, 0xb5, 0xf8, 0x2f, 0x62, /* 72 - 79 */
+ 0x97, 0xda, 0x0d, 0x40, 0xee, 0xa3, 0x74, 0x39, /* 80 - 87 */
+ 0x65, 0x28, 0xff, 0xb2, 0x1c, 0x51, 0x86, 0xcb, /* 88 - 95 */
+ 0x21, 0x6c, 0xbb, 0xf6, 0x58, 0x15, 0xc2, 0x8f, /* 96 - 103 */
+ 0xd3, 0x9e, 0x49, 0x04, 0xaa, 0xe7, 0x30, 0x7d, /* 104 - 111 */
+ 0x88, 0xc5, 0x12, 0x5f, 0xf1, 0xbc, 0x6b, 0x26, /* 112 - 119 */
+ 0x7a, 0x37, 0xe0, 0xad, 0x03, 0x4e, 0x99, 0xd4, /* 120 - 127 */
+ 0x7c, 0x31, 0xe6, 0xab, 0x05, 0x48, 0x9f, 0xd2, /* 128 - 135 */
+ 0x8e, 0xc3, 0x14, 0x59, 0xf7, 0xba, 0x6d, 0x20, /* 136 - 143 */
+ 0xd5, 0x98, 0x4f, 0x02, 0xac, 0xe1, 0x36, 0x7b, /* 144 - 151 */
+ 0x27, 0x6a, 0xbd, 0xf0, 0x5e, 0x13, 0xc4, 0x89, /* 152 - 159 */
+ 0x63, 0x2e, 0xf9, 0xb4, 0x1a, 0x57, 0x80, 0xcd, /* 160 - 167 */
+ 0x91, 0xdc, 0x0b, 0x46, 0xe8, 0xa5, 0x72, 0x3f, /* 168 - 175 */
+ 0xca, 0x87, 0x50, 0x1d, 0xb3, 0xfe, 0x29, 0x64, /* 176 - 183 */
+ 0x38, 0x75, 0xa2, 0xef, 0x41, 0x0c, 0xdb, 0x96, /* 184 - 191 */
+ 0x42, 0x0f, 0xd8, 0x95, 0x3b, 0x76, 0xa1, 0xec, /* 192 - 199 */
+ 0xb0, 0xfd, 0x2a, 0x67, 0xc9, 0x84, 0x53, 0x1e, /* 200 - 207 */
+ 0xeb, 0xa6, 0x71, 0x3c, 0x92, 0xdf, 0x08, 0x45, /* 208 - 215 */
+ 0x19, 0x54, 0x83, 0xce, 0x60, 0x2d, 0xfa, 0xb7, /* 216 - 223 */
+ 0x5d, 0x10, 0xc7, 0x8a, 0x24, 0x69, 0xbe, 0xf3, /* 224 - 231 */
+ 0xaf, 0xe2, 0x35, 0x78, 0xd6, 0x9b, 0x4c, 0x01, /* 232 - 239 */
+ 0xf4, 0xb9, 0x6e, 0x23, 0x8d, 0xc0, 0x17, 0x5a, /* 240 - 247 */
+ 0x06, 0x4b, 0x9c, 0xd1, 0x7f, 0x32, 0xe5, 0xa8 /* 248 - 255 */
+};
+
+/* BPT/BRA helpers */
- if (ch_count)
+#define SDW_CDNS_BRA_HDR 6 /* defined by MIPI */
+#define SDW_CDNS_BRA_HDR_CRC 1 /* defined by MIPI */
+#define SDW_CDNS_BRA_HDR_CRC_PAD 1 /* Cadence only */
+#define SDW_CDNS_BRA_HDR_RESP 1 /* defined by MIPI */
+#define SDW_CDNS_BRA_HDR_RESP_PAD 1 /* Cadence only */
+
+#define SDW_CDNS_BRA_DATA_PAD 1 /* Cadence only */
+#define SDW_CDNS_BRA_DATA_CRC 1 /* defined by MIPI */
+#define SDW_CDNS_BRA_DATA_CRC_PAD 1 /* Cadence only */
+
+#define SDW_CDNS_BRA_FOOTER_RESP 1 /* defined by MIPI */
+#define SDW_CDNS_BRA_FOOTER_RESP_PAD 1 /* Cadence only */
+
+#define SDW_CDNS_WRITE_PDI1_BUFFER_SIZE \
+ ((SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_HDR_RESP_PAD + \
+ SDW_CDNS_BRA_FOOTER_RESP + SDW_CDNS_BRA_FOOTER_RESP_PAD) * 2)
+
+#define SDW_CDNS_READ_PDI0_BUFFER_SIZE \
+ ((SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD) * 2)
+
+static unsigned int sdw_cdns_bra_actual_data_size(unsigned int allocated_bytes_per_frame)
+{
+ unsigned int total;
+
+ if (allocated_bytes_per_frame < (SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC +
+ SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_DATA_CRC +
+ SDW_CDNS_BRA_FOOTER_RESP))
return 0;
- return pdis;
+ total = allocated_bytes_per_frame - SDW_CDNS_BRA_HDR - SDW_CDNS_BRA_HDR_CRC -
+ SDW_CDNS_BRA_HDR_RESP - SDW_CDNS_BRA_DATA_CRC - SDW_CDNS_BRA_FOOTER_RESP;
+
+ return total;
}
-/**
- * sdw_cdns_get_stream() - Get stream information
- *
- * @cdns: Cadence instance
- * @stream: Stream to be allocated
- * @ch: Channel count
- * @dir: Data direction
- */
-int sdw_cdns_get_stream(struct sdw_cdns *cdns,
- struct sdw_cdns_streams *stream,
- u32 ch, u32 dir)
+static unsigned int sdw_cdns_write_pdi0_buffer_size(unsigned int actual_data_size)
{
- int pdis = 0;
+ unsigned int total;
- if (dir == SDW_DATA_DIR_RX)
- pdis = cdns_get_num_pdi(cdns, stream->in, stream->num_in, ch);
- else
- pdis = cdns_get_num_pdi(cdns, stream->out, stream->num_out, ch);
+ total = SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD;
+
+ total += actual_data_size;
+ if (actual_data_size & 1)
+ total += SDW_CDNS_BRA_DATA_PAD;
- /* check if we found PDI, else find in bi-directional */
- if (!pdis)
- pdis = cdns_get_num_pdi(cdns, stream->bd, stream->num_bd, ch);
+ total += SDW_CDNS_BRA_DATA_CRC + SDW_CDNS_BRA_DATA_CRC_PAD;
- return pdis;
+ return total * 2;
}
-EXPORT_SYMBOL(sdw_cdns_get_stream);
-/**
- * sdw_cdns_alloc_stream() - Allocate a stream
- *
- * @cdns: Cadence instance
- * @stream: Stream to be allocated
- * @port: Cadence data port
- * @ch: Channel count
- * @dir: Data direction
- */
-int sdw_cdns_alloc_stream(struct sdw_cdns *cdns,
- struct sdw_cdns_streams *stream,
- struct sdw_cdns_port *port, u32 ch, u32 dir)
+static unsigned int sdw_cdns_read_pdi1_buffer_size(unsigned int actual_data_size)
{
- struct sdw_cdns_pdi *pdi = NULL;
+ unsigned int total;
- if (dir == SDW_DATA_DIR_RX)
- pdi = cdns_find_pdi(cdns, stream->num_in, stream->in);
- else
- pdi = cdns_find_pdi(cdns, stream->num_out, stream->out);
+ total = SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_HDR_RESP_PAD;
- /* check if we found a PDI, else find in bi-directional */
- if (!pdi)
- pdi = cdns_find_pdi(cdns, stream->num_bd, stream->bd);
+ total += actual_data_size;
+ if (actual_data_size & 1)
+ total += SDW_CDNS_BRA_DATA_PAD;
- if (!pdi)
+ total += SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD;
+
+ total += SDW_CDNS_BRA_FOOTER_RESP + SDW_CDNS_BRA_FOOTER_RESP_PAD;
+
+ return total * 2;
+}
+
+int sdw_cdns_bpt_find_buffer_sizes(int command, /* 0: write, 1: read */
+ int row, int col, unsigned int data_bytes,
+ unsigned int requested_bytes_per_frame,
+ unsigned int *data_per_frame, unsigned int *pdi0_buffer_size,
+ unsigned int *pdi1_buffer_size, unsigned int *num_frames)
+{
+ unsigned int bpt_bits = row * (col - 1);
+ unsigned int bpt_bytes = bpt_bits >> 3;
+ unsigned int actual_bpt_bytes;
+ unsigned int pdi0_tx_size;
+ unsigned int pdi1_rx_size;
+ unsigned int remainder;
+
+ if (!data_bytes)
+ return -EINVAL;
+
+ actual_bpt_bytes = sdw_cdns_bra_actual_data_size(bpt_bytes);
+ if (!actual_bpt_bytes)
+ return -EINVAL;
+
+ if (data_bytes < actual_bpt_bytes)
+ actual_bpt_bytes = data_bytes;
+
+ /*
+ * the caller may want to set the number of bytes per frame,
+ * allow when possible
+ */
+ if (requested_bytes_per_frame < actual_bpt_bytes)
+ actual_bpt_bytes = requested_bytes_per_frame;
+
+ *data_per_frame = actual_bpt_bytes;
+
+ if (command == 0) {
+ /*
+ * for writes we need to send all the data_bytes per frame,
+ * even for the last frame which may only transport fewer bytes
+ */
+
+ *num_frames = DIV_ROUND_UP(data_bytes, actual_bpt_bytes);
+
+ pdi0_tx_size = sdw_cdns_write_pdi0_buffer_size(actual_bpt_bytes);
+ pdi1_rx_size = SDW_CDNS_WRITE_PDI1_BUFFER_SIZE;
+
+ *pdi0_buffer_size = pdi0_tx_size * *num_frames;
+ *pdi1_buffer_size = pdi1_rx_size * *num_frames;
+ } else {
+ /*
+ * for reads we need to retrieve only what is requested in the BPT
+ * header, so the last frame needs to be special-cased
+ */
+ *num_frames = data_bytes / actual_bpt_bytes;
+
+ pdi0_tx_size = SDW_CDNS_READ_PDI0_BUFFER_SIZE;
+ pdi1_rx_size = sdw_cdns_read_pdi1_buffer_size(actual_bpt_bytes);
+
+ *pdi0_buffer_size = pdi0_tx_size * *num_frames;
+ *pdi1_buffer_size = pdi1_rx_size * *num_frames;
+
+ remainder = data_bytes % actual_bpt_bytes;
+ if (remainder) {
+ pdi0_tx_size = SDW_CDNS_READ_PDI0_BUFFER_SIZE;
+ pdi1_rx_size = sdw_cdns_read_pdi1_buffer_size(remainder);
+
+ *num_frames = *num_frames + 1;
+ *pdi0_buffer_size += pdi0_tx_size;
+ *pdi1_buffer_size += pdi1_rx_size;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_bpt_find_buffer_sizes);
+
+static int sdw_cdns_copy_write_data(u8 *data, int data_size, u8 *dma_buffer, int dma_buffer_size)
+{
+ /*
+ * the implementation copies the data one byte at a time. Experiments with
+ * two bytes at a time did not seem to improve the performance
+ */
+ int i, j;
+
+ /* size check to prevent out of bounds access */
+ i = data_size - 1;
+ j = (2 * i) - (i & 1);
+ if (data_size & 1)
+ j++;
+ j += 2;
+ if (j >= dma_buffer_size)
+ return -EINVAL;
+
+ /* copy data */
+ for (i = 0; i < data_size; i++) {
+ j = (2 * i) - (i & 1);
+ dma_buffer[j] = data[i];
+ }
+ /* add required pad */
+ if (data_size & 1)
+ dma_buffer[++j] = 0;
+ /* skip last two bytes */
+ j += 2;
+
+ /* offset and data are off-by-one */
+ return j + 1;
+}
+
+static int sdw_cdns_prepare_write_pd0_buffer(u8 *header, unsigned int header_size,
+ u8 *data, unsigned int data_size,
+ u8 *dma_buffer, unsigned int dma_buffer_size,
+ unsigned int *dma_data_written,
+ unsigned int frame_counter)
+{
+ int data_written;
+ u8 *last_byte;
+ u8 crc;
+
+ *dma_data_written = 0;
+
+ data_written = sdw_cdns_copy_write_data(header, header_size, dma_buffer, dma_buffer_size);
+ if (data_written < 0)
+ return data_written;
+ dma_buffer[3] = BIT(7);
+ dma_buffer[3] |= frame_counter & GENMASK(3, 0);
+
+ dma_buffer += data_written;
+ dma_buffer_size -= data_written;
+ *dma_data_written += data_written;
+
+ crc = SDW_CRC8_SEED;
+ crc = crc8(sdw_crc8_lookup_msb, header, header_size, crc);
+
+ data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size);
+ if (data_written < 0)
+ return data_written;
+ dma_buffer += data_written;
+ dma_buffer_size -= data_written;
+ *dma_data_written += data_written;
+
+ data_written = sdw_cdns_copy_write_data(data, data_size, dma_buffer, dma_buffer_size);
+ if (data_written < 0)
+ return data_written;
+ dma_buffer += data_written;
+ dma_buffer_size -= data_written;
+ *dma_data_written += data_written;
+
+ crc = SDW_CRC8_SEED;
+ crc = crc8(sdw_crc8_lookup_msb, data, data_size, crc);
+ data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size);
+ if (data_written < 0)
+ return data_written;
+ dma_buffer += data_written;
+ dma_buffer_size -= data_written;
+ *dma_data_written += data_written;
+
+ /* tag last byte */
+ last_byte = dma_buffer - 1;
+ last_byte[0] = BIT(6);
+
+ return 0;
+}
+
+static int sdw_cdns_prepare_read_pd0_buffer(u8 *header, unsigned int header_size,
+ u8 *dma_buffer, unsigned int dma_buffer_size,
+ unsigned int *dma_data_written,
+ unsigned int frame_counter)
+{
+ int data_written;
+ u8 *last_byte;
+ u8 crc;
+
+ *dma_data_written = 0;
+
+ data_written = sdw_cdns_copy_write_data(header, header_size, dma_buffer, dma_buffer_size);
+ if (data_written < 0)
+ return data_written;
+ dma_buffer[3] = BIT(7);
+ dma_buffer[3] |= frame_counter & GENMASK(3, 0);
+
+ dma_buffer += data_written;
+ dma_buffer_size -= data_written;
+ *dma_data_written += data_written;
+
+ crc = SDW_CRC8_SEED;
+ crc = crc8(sdw_crc8_lookup_msb, header, header_size, crc);
+
+ data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size);
+ if (data_written < 0)
+ return data_written;
+ dma_buffer += data_written;
+ dma_buffer_size -= data_written;
+ *dma_data_written += data_written;
+
+ /* tag last byte */
+ last_byte = dma_buffer - 1;
+ last_byte[0] = BIT(6);
+
+ return 0;
+}
+
+#define CDNS_BPT_ROLLING_COUNTER_START 1
+
+int sdw_cdns_prepare_write_dma_buffer(u8 dev_num, u32 start_register, u8 *data, int data_size,
+ int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
+ int *dma_buffer_total_bytes)
+{
+ int total_dma_data_written = 0;
+ u8 *p_dma_buffer = dma_buffer;
+ u8 header[SDW_CDNS_BRA_HDR];
+ int dma_data_written;
+ u8 *p_data = data;
+ u8 counter;
+ int ret;
+
+ counter = CDNS_BPT_ROLLING_COUNTER_START;
+
+ header[0] = BIT(1); /* write command: BIT(1) set */
+ header[0] |= GENMASK(7, 6); /* header is active */
+ header[0] |= (dev_num << 2);
+
+ while (data_size >= data_per_frame) {
+ header[1] = data_per_frame;
+ header[2] = start_register >> 24 & 0xFF;
+ header[3] = start_register >> 16 & 0xFF;
+ header[4] = start_register >> 8 & 0xFF;
+ header[5] = start_register >> 0 & 0xFF;
+
+ ret = sdw_cdns_prepare_write_pd0_buffer(header, SDW_CDNS_BRA_HDR,
+ p_data, data_per_frame,
+ p_dma_buffer, dma_buffer_size,
+ &dma_data_written, counter);
+ if (ret < 0)
+ return ret;
+
+ counter++;
+
+ p_data += data_per_frame;
+ data_size -= data_per_frame;
+
+ p_dma_buffer += dma_data_written;
+ dma_buffer_size -= dma_data_written;
+ total_dma_data_written += dma_data_written;
+
+ start_register += data_per_frame;
+ }
+
+ if (data_size) {
+ header[1] = data_size;
+ header[2] = start_register >> 24 & 0xFF;
+ header[3] = start_register >> 16 & 0xFF;
+ header[4] = start_register >> 8 & 0xFF;
+ header[5] = start_register >> 0 & 0xFF;
+
+ ret = sdw_cdns_prepare_write_pd0_buffer(header, SDW_CDNS_BRA_HDR,
+ p_data, data_size,
+ p_dma_buffer, dma_buffer_size,
+ &dma_data_written, counter);
+ if (ret < 0)
+ return ret;
+
+ total_dma_data_written += dma_data_written;
+ }
+
+ *dma_buffer_total_bytes = total_dma_data_written;
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_prepare_write_dma_buffer);
+
+int sdw_cdns_prepare_read_dma_buffer(u8 dev_num, u32 start_register, int data_size,
+ int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
+ int *dma_buffer_total_bytes)
+{
+ int total_dma_data_written = 0;
+ u8 *p_dma_buffer = dma_buffer;
+ u8 header[SDW_CDNS_BRA_HDR];
+ int dma_data_written;
+ u8 counter;
+ int ret;
+
+ counter = CDNS_BPT_ROLLING_COUNTER_START;
+
+ header[0] = 0; /* read command: BIT(1) cleared */
+ header[0] |= GENMASK(7, 6); /* header is active */
+ header[0] |= (dev_num << 2);
+
+ while (data_size >= data_per_frame) {
+ header[1] = data_per_frame;
+ header[2] = start_register >> 24 & 0xFF;
+ header[3] = start_register >> 16 & 0xFF;
+ header[4] = start_register >> 8 & 0xFF;
+ header[5] = start_register >> 0 & 0xFF;
+
+ ret = sdw_cdns_prepare_read_pd0_buffer(header, SDW_CDNS_BRA_HDR, p_dma_buffer,
+ dma_buffer_size, &dma_data_written,
+ counter);
+ if (ret < 0)
+ return ret;
+
+ counter++;
+
+ data_size -= data_per_frame;
+
+ p_dma_buffer += dma_data_written;
+ dma_buffer_size -= dma_data_written;
+ total_dma_data_written += dma_data_written;
+
+ start_register += data_per_frame;
+ }
+
+ if (data_size) {
+ header[1] = data_size;
+ header[2] = start_register >> 24 & 0xFF;
+ header[3] = start_register >> 16 & 0xFF;
+ header[4] = start_register >> 8 & 0xFF;
+ header[5] = start_register >> 0 & 0xFF;
+
+ ret = sdw_cdns_prepare_read_pd0_buffer(header, SDW_CDNS_BRA_HDR, p_dma_buffer,
+ dma_buffer_size, &dma_data_written,
+ counter);
+ if (ret < 0)
+ return ret;
+
+ total_dma_data_written += dma_data_written;
+ }
+
+ *dma_buffer_total_bytes = total_dma_data_written;
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_prepare_read_dma_buffer);
+
+static int check_counter(u32 val, u8 counter)
+{
+ u8 frame;
+
+ frame = (val >> 24) & GENMASK(3, 0);
+ if (counter != frame)
return -EIO;
+ return 0;
+}
+
+static int check_response(u32 val)
+{
+ u8 response;
- port->pdi = pdi;
- pdi->l_ch_num = 0;
- pdi->h_ch_num = ch - 1;
- pdi->dir = dir;
- pdi->ch_count = ch;
+ response = (val >> 3) & GENMASK(1, 0);
+ if (response == 0) /* Ignored */
+ return -ENODATA;
+ if (response != 1) /* ACK */
+ return -EIO;
return 0;
}
-EXPORT_SYMBOL(sdw_cdns_alloc_stream);
-void sdw_cdns_shutdown(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
+static int check_frame_start(u32 header, u8 counter)
{
- struct sdw_cdns_dma_data *dma;
+ int ret;
+
+ /* check frame_start marker */
+ if (!(header & BIT(31)))
+ return -EIO;
+
+ ret = check_counter(header, counter);
+ if (ret < 0)
+ return ret;
+
+ return check_response(header);
+}
+
+static int check_frame_end(u32 footer)
+{
+ /* check frame_end marker */
+ if (!(footer & BIT(30)))
+ return -EIO;
+
+ return check_response(footer);
+}
+
+int sdw_cdns_check_write_response(struct device *dev, u8 *dma_buffer,
+ int dma_buffer_size, int num_frames)
+{
+ u32 *p_data;
+ int counter;
+ u32 header;
+ u32 footer;
+ int ret;
+ int i;
+
+ /* paranoia check on buffer size */
+ if (dma_buffer_size != num_frames * 8)
+ return -EINVAL;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
- return;
+ counter = CDNS_BPT_ROLLING_COUNTER_START;
+ p_data = (u32 *)dma_buffer;
- snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(dma);
+ for (i = 0; i < num_frames; i++) {
+ header = *p_data++;
+ footer = *p_data++;
+
+ ret = check_frame_start(header, counter);
+ if (ret < 0) {
+ dev_err(dev, "%s: bad frame %d/%d start header %x\n",
+ __func__, i, num_frames, header);
+ return ret;
+ }
+
+ ret = check_frame_end(footer);
+ if (ret < 0) {
+ dev_err(dev, "%s: bad frame %d/%d end footer %x\n",
+ __func__, i, num_frames, footer);
+ return ret;
+ }
+
+ counter++;
+ counter &= GENMASK(3, 0);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_check_write_response);
+
+static u8 extract_read_data(u32 *data, int num_bytes, u8 *buffer)
+{
+ u32 val;
+ int i;
+ u8 crc;
+ u8 b0;
+ u8 b1;
+
+ crc = SDW_CRC8_SEED;
+
+ /* process two bytes at a time */
+ for (i = 0; i < num_bytes / 2; i++) {
+ val = *data++;
+
+ b0 = val & 0xff;
+ b1 = (val >> 8) & 0xff;
+
+ *buffer++ = b0;
+ crc = crc8(sdw_crc8_lookup_msb, &b0, 1, crc);
+
+ *buffer++ = b1;
+ crc = crc8(sdw_crc8_lookup_msb, &b1, 1, crc);
+ }
+ /* handle remaining byte if it exists */
+ if (num_bytes & 1) {
+ val = *data;
+
+ b0 = val & 0xff;
+
+ *buffer++ = b0;
+ crc = crc8(sdw_crc8_lookup_msb, &b0, 1, crc);
+ }
+ return crc;
+}
+
+int sdw_cdns_check_read_response(struct device *dev, u8 *dma_buffer, int dma_buffer_size,
+ u8 *buffer, int buffer_size, int num_frames, int data_per_frame)
+{
+ int total_num_bytes = 0;
+ u32 *p_data;
+ u8 *p_buf;
+ int counter;
+ u32 header;
+ u32 footer;
+ u8 expected_crc;
+ u8 crc;
+ int len;
+ int ret;
+ int i;
+
+ counter = CDNS_BPT_ROLLING_COUNTER_START;
+ p_data = (u32 *)dma_buffer;
+ p_buf = buffer;
+
+ for (i = 0; i < num_frames; i++) {
+ header = *p_data++;
+
+ ret = check_frame_start(header, counter);
+ if (ret < 0) {
+ dev_err(dev, "%s: bad frame %d/%d start header %x\n",
+ __func__, i, num_frames, header);
+ return ret;
+ }
+
+ len = data_per_frame;
+ if (total_num_bytes + data_per_frame > buffer_size)
+ len = buffer_size - total_num_bytes;
+
+ crc = extract_read_data(p_data, len, p_buf);
+
+ p_data += (len + 1) / 2;
+ expected_crc = *p_data++ & 0xff;
+
+ if (crc != expected_crc) {
+ dev_err(dev, "%s: bad frame %d/%d crc %#x expected %#x\n",
+ __func__, i, num_frames, crc, expected_crc);
+ return -EIO;
+ }
+
+ p_buf += len;
+ total_num_bytes += len;
+
+ footer = *p_data++;
+ ret = check_frame_end(footer);
+ if (ret < 0) {
+ dev_err(dev, "%s: bad frame %d/%d end footer %x\n",
+ __func__, i, num_frames, footer);
+ return ret;
+ }
+
+ counter++;
+ counter &= GENMASK(3, 0);
+ }
+ return 0;
}
-EXPORT_SYMBOL(sdw_cdns_shutdown);
+EXPORT_SYMBOL(sdw_cdns_check_read_response);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Cadence Soundwire Library");
diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h
index eb902b19c5a4..9373426c7f63 100644
--- a/drivers/soundwire/cadence_master.h
+++ b/drivers/soundwire/cadence_master.h
@@ -1,24 +1,33 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
-// Copyright(c) 2015-17 Intel Corporation.
+/* 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
+
+#define SDW_CADENCE_MCP_IP_OFFSET 0x4000
+
/**
* struct sdw_cdns_pdi: PDI (Physical Data Interface) instance
*
- * @assigned: pdi assigned
* @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, PDM or PCM
+ * @type: stream type, (only PCM supported)
*/
struct sdw_cdns_pdi {
- bool assigned;
int num;
int intel_alh_id;
int l_ch_num;
@@ -29,23 +38,6 @@ struct sdw_cdns_pdi {
};
/**
- * struct sdw_cdns_port: Cadence port structure
- *
- * @num: port number
- * @assigned: port assigned
- * @ch: channel count
- * @direction: data port direction
- * @pdi: pdi for this port
- */
-struct sdw_cdns_port {
- unsigned int num;
- bool assigned;
- unsigned int ch;
- enum sdw_data_direction direction;
- struct sdw_cdns_pdi *pdi;
-};
-
-/**
* struct sdw_cdns_streams: Cadence stream data structure
*
* @num_bd: number of bidirectional streams
@@ -78,37 +70,36 @@ struct sdw_cdns_streams {
* @pcm_bd: number of bidirectional PCM streams supported
* @pcm_in: number of input PCM streams supported
* @pcm_out: number of output PCM streams supported
- * @pdm_bd: number of bidirectional PDM streams supported
- * @pdm_in: number of input PDM streams supported
- * @pdm_out: number of output PDM streams supported
*/
struct sdw_cdns_stream_config {
unsigned int pcm_bd;
unsigned int pcm_in;
unsigned int pcm_out;
- unsigned int pdm_bd;
- unsigned int pdm_in;
- unsigned int pdm_out;
};
/**
- * struct sdw_cdns_dma_data: Cadence DMA data
+ * struct sdw_cdns_dai_runtime: Cadence DAI runtime data
*
* @name: SoundWire stream name
- * @nr_ports: Number of ports
- * @port: Ports
+ * @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
+ * @direction: stream direction
*/
-struct sdw_cdns_dma_data {
+struct sdw_cdns_dai_runtime {
char *name;
struct sdw_stream_runtime *stream;
- int nr_ports;
- struct sdw_cdns_port **port;
+ struct sdw_cdns_pdi *pdi;
struct sdw_bus *bus;
enum sdw_stream_type stream_type;
int link_id;
+ bool suspended;
+ bool paused;
+ int direction;
};
/**
@@ -116,36 +107,56 @@ struct sdw_cdns_dma_data {
* @dev: Linux device
* @bus: Bus handle
* @instance: instance number
+ * @ip_offset: version-dependent offset to access IP_MCP registers and fields
* @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
- * @pdm: PDM streams
* @registers: Cadence registers
* @link_up: Link status
* @msg_count: Messages sent on bus
+ * @dai_runtime_array: runtime context for each allocated DAI.
+ * @status_update_lock: protect concurrency between interrupt-based and delayed work
+ * status update
*/
struct sdw_cdns {
struct device *dev;
struct sdw_bus bus;
unsigned int instance;
- u32 response_buf[0x80];
+ u32 ip_offset;
+
+ /*
+ * 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;
- struct sdw_cdns_streams pdm;
+
+ 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 delayed_work attach_dwork;
+
+ struct list_head list;
+
+ struct sdw_cdns_dai_runtime **dai_runtime_array;
+
+ struct mutex status_update_lock; /* add mutual exclusion to sdw_handle_slave_status() */
};
#define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus)
@@ -153,47 +164,68 @@ struct sdw_cdns {
/* 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_soft_reset(struct sdw_cdns *cdns);
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_enable_interrupt(struct sdw_cdns *cdns);
-
-int sdw_cdns_get_stream(struct sdw_cdns *cdns,
- struct sdw_cdns_streams *stream,
- u32 ch, u32 dir);
-int sdw_cdns_alloc_stream(struct sdw_cdns *cdns,
- struct sdw_cdns_streams *stream,
- struct sdw_cdns_port *port, u32 ch, u32 dir);
-void sdw_cdns_config_stream(struct sdw_cdns *cdns, struct sdw_cdns_port *port,
- u32 ch, u32 dir, struct sdw_cdns_pdi *pdi);
-
-void sdw_cdns_shutdown(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai);
-int sdw_cdns_pcm_set_stream(struct snd_soc_dai *dai,
- void *stream, int direction);
-int sdw_cdns_pdm_set_stream(struct snd_soc_dai *dai,
- void *stream, int direction);
+ 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);
-enum sdw_command_response
-cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num);
+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_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);
+cdns_xfer_msg_defer(struct sdw_bus *bus);
-enum sdw_command_response
-cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num);
+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, bool pcm, int direction);
+ void *stream, int direction);
+
+void sdw_cdns_check_self_clearing_bits(struct sdw_cdns *cdns, const char *string,
+ bool initial_delay, int reset_iterations);
+
+void sdw_cdns_config_update(struct sdw_cdns *cdns);
+int sdw_cdns_config_update_set_wait(struct sdw_cdns *cdns);
+
+/* SoundWire BPT/BRA helpers to format data */
+int sdw_cdns_bpt_find_buffer_sizes(int command, /* 0: write, 1: read */
+ int row, int col, unsigned int data_bytes,
+ unsigned int requested_bytes_per_frame,
+ unsigned int *data_per_frame, unsigned int *pdi0_buffer_size,
+ unsigned int *pdi1_buffer_size, unsigned int *num_frames);
+
+int sdw_cdns_prepare_write_dma_buffer(u8 dev_num, u32 start_register, u8 *data, int data_size,
+ int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
+ int *dma_buffer_total_bytes);
+
+int sdw_cdns_prepare_read_dma_buffer(u8 dev_num, u32 start_register, int data_size,
+ int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
+ int *dma_buffer_total_bytes);
+
+int sdw_cdns_check_write_response(struct device *dev, u8 *dma_buffer,
+ int dma_buffer_size, int num_frames);
+
+int sdw_cdns_check_read_response(struct device *dev, u8 *dma_buffer, int dma_buffer_size,
+ u8 *buffer, int buffer_size, int num_frames, int data_per_frame);
#endif /* __SDW_CADENCE_H */
diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c
new file mode 100644
index 000000000000..1e0f9318b616
--- /dev/null
+++ b/drivers/soundwire/debugfs.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2017-2019 Intel Corporation.
+
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/firmware.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/string_choices.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->controller_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_no_pm(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;
+ ssize_t ret;
+ int i, j;
+
+ char *buf __free(kfree) = kzalloc(RD_BUF, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = pm_runtime_get_sync(&slave->dev);
+ if (ret < 0 && ret != -EACCES) {
+ pm_runtime_put_noidle(&slave->dev);
+ return ret;
+ }
+
+ 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_BUS_CLOCK_BASE; 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);
+ for (i = SDW_SCP_SDCA_INT1; i <= SDW_SCP_SDCA_INTMASK4; i++)
+ ret += sdw_sprintf(slave, buf, ret, i);
+ for (i = SDW_SCP_FRAMECTRL_B0; i <= SDW_SCP_BUSCLOCK_SCALE_B0; i++)
+ ret += sdw_sprintf(slave, buf, ret, i);
+ for (i = SDW_SCP_FRAMECTRL_B1; i <= SDW_SCP_BUSCLOCK_SCALE_B1; i++)
+ ret += sdw_sprintf(slave, buf, ret, i);
+ for (i = SDW_SCP_PHY_OUT_CTRL_0; i <= SDW_SCP_PHY_OUT_CTRL_7; 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);
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put(&slave->dev);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(sdw_slave_reg);
+
+#define MAX_CMD_BYTES (1024 * 1024)
+
+static int cmd;
+static int cmd_type;
+static u32 start_addr;
+static size_t num_bytes;
+static u8 read_buffer[MAX_CMD_BYTES];
+static char *firmware_file;
+
+static int set_command(void *data, u64 value)
+{
+ struct sdw_slave *slave = data;
+
+ if (value > 1)
+ return -EINVAL;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ dev_dbg(&slave->dev, "command: %s\n", str_read_write(value));
+ cmd = value;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(set_command_fops, NULL,
+ set_command, "%llu\n");
+
+static int set_command_type(void *data, u64 value)
+{
+ struct sdw_slave *slave = data;
+
+ if (value > 1)
+ return -EINVAL;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ dev_dbg(&slave->dev, "command type: %s\n", value ? "BRA" : "Column0");
+
+ cmd_type = (int)value;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(set_command_type_fops, NULL,
+ set_command_type, "%llu\n");
+
+static int set_start_address(void *data, u64 value)
+{
+ struct sdw_slave *slave = data;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ dev_dbg(&slave->dev, "start address %#llx\n", value);
+
+ start_addr = value;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(set_start_address_fops, NULL,
+ set_start_address, "%llu\n");
+
+static int set_num_bytes(void *data, u64 value)
+{
+ struct sdw_slave *slave = data;
+
+ if (value == 0 || value > MAX_CMD_BYTES)
+ return -EINVAL;
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ dev_dbg(&slave->dev, "number of bytes %lld\n", value);
+
+ num_bytes = value;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(set_num_bytes_fops, NULL,
+ set_num_bytes, "%llu\n");
+
+static int do_bpt_sequence(struct sdw_slave *slave, bool write, u8 *buffer)
+{
+ struct sdw_bpt_msg msg = {0};
+
+ msg.addr = start_addr;
+ msg.len = num_bytes;
+ msg.dev_num = slave->dev_num;
+ if (write)
+ msg.flags = SDW_MSG_FLAG_WRITE;
+ else
+ msg.flags = SDW_MSG_FLAG_READ;
+ msg.buf = buffer;
+
+ return sdw_bpt_send_sync(slave->bus, slave, &msg);
+}
+
+static int cmd_go(void *data, u64 value)
+{
+ const struct firmware *fw = NULL;
+ struct sdw_slave *slave = data;
+ ktime_t start_t;
+ ktime_t finish_t;
+ int ret;
+
+ if (value != 1)
+ return -EINVAL;
+
+ /* one last check */
+ if (start_addr > SDW_REG_MAX ||
+ num_bytes == 0 || num_bytes > MAX_CMD_BYTES)
+ return -EINVAL;
+
+ ret = pm_runtime_get_sync(&slave->dev);
+ if (ret < 0 && ret != -EACCES) {
+ pm_runtime_put_noidle(&slave->dev);
+ return ret;
+ }
+
+ if (cmd == 0) {
+ ret = request_firmware(&fw, firmware_file, &slave->dev);
+ if (ret < 0) {
+ dev_err(&slave->dev, "firmware %s not found\n", firmware_file);
+ goto out;
+ }
+ if (fw->size < num_bytes) {
+ dev_err(&slave->dev,
+ "firmware %s: firmware size %zd, desired %zd\n",
+ firmware_file, fw->size, num_bytes);
+ goto out;
+ }
+ }
+
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ dev_dbg(&slave->dev, "starting command\n");
+ start_t = ktime_get();
+
+ if (cmd == 0) {
+ if (cmd_type)
+ ret = do_bpt_sequence(slave, true, (u8 *)fw->data);
+ else
+ ret = sdw_nwrite_no_pm(slave, start_addr, num_bytes, fw->data);
+ } else {
+ memset(read_buffer, 0, sizeof(read_buffer));
+
+ if (cmd_type)
+ ret = do_bpt_sequence(slave, false, read_buffer);
+ else
+ ret = sdw_nread_no_pm(slave, start_addr, num_bytes, read_buffer);
+ }
+
+ finish_t = ktime_get();
+
+ dev_dbg(&slave->dev, "command completed, num_byte %zu status %d, time %lld ms\n",
+ num_bytes, ret, div_u64(finish_t - start_t, NSEC_PER_MSEC));
+
+out:
+ if (fw)
+ release_firmware(fw);
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put(&slave->dev);
+
+ return ret;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(cmd_go_fops, NULL,
+ cmd_go, "%llu\n");
+
+#define MAX_LINE_LEN 128
+
+static int read_buffer_show(struct seq_file *s_file, void *data)
+{
+ char buf[MAX_LINE_LEN];
+ int i;
+
+ if (num_bytes == 0 || num_bytes > MAX_CMD_BYTES)
+ return -EINVAL;
+
+ for (i = 0; i < num_bytes; i++) {
+ scnprintf(buf, MAX_LINE_LEN, "address %#x val 0x%02x\n",
+ start_addr + i, read_buffer[i]);
+ seq_printf(s_file, "%s", buf);
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(read_buffer);
+
+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);
+
+ /* interface to send arbitrary commands */
+ debugfs_create_file("command", 0200, d, slave, &set_command_fops);
+ debugfs_create_file("command_type", 0200, d, slave, &set_command_type_fops);
+ debugfs_create_file("start_address", 0200, d, slave, &set_start_address_fops);
+ debugfs_create_file("num_bytes", 0200, d, slave, &set_num_bytes_fops);
+ debugfs_create_file("go", 0200, d, slave, &cmd_go_fops);
+
+ debugfs_create_file("read_buffer", 0400, d, slave, &read_buffer_fops);
+ firmware_file = NULL;
+ debugfs_create_str("firmware_file", 0200, d, &firmware_file);
+
+ 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 000000000000..91ab97a456fa
--- /dev/null
+++ b/drivers/soundwire/dmi-quirks.c
@@ -0,0 +1,179 @@
+// 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,
+ },
+ {
+ /* quirk used for NUC15 LAPRC710 skew */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_BOARD_NAME, "LAPRC710"),
+ },
+ .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 000000000000..c18f0c16f929
--- /dev/null
+++ b/drivers/soundwire/generic_bandwidth_allocation.c
@@ -0,0 +1,689 @@
+// 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/bitops.h>
+#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;
+ unsigned int lane;
+ int full_bw;
+ int payload_bw;
+ int hwidth;
+};
+
+struct sdw_group {
+ unsigned int count;
+ unsigned int max_size;
+ unsigned int *rates;
+ unsigned int *lanes;
+};
+
+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) {
+ if (p_rt->lane != t_data->lane)
+ continue;
+
+ ch = hweight32(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, p_rt->lane);
+
+ 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;
+ }
+ }
+}
+EXPORT_SYMBOL(sdw_compute_slave_ports);
+
+static void sdw_compute_dp0_slave_ports(struct sdw_master_runtime *m_rt)
+{
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_port_runtime *p_rt;
+
+ 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) {
+ sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, false,
+ SDW_BLK_GRP_CNT_1, bus->params.col, 0, 0, 1,
+ bus->params.col - 1, SDW_BLK_PKG_PER_PORT, 0x0);
+
+ sdw_fill_port_params(&p_rt->port_params, p_rt->num, bus->params.col - 1,
+ SDW_PORT_FLOW_MODE_ISOCH, SDW_PORT_DATA_MODE_NORMAL);
+ }
+ }
+}
+
+static void sdw_compute_dp0_master_ports(struct sdw_master_runtime *m_rt)
+{
+ struct sdw_port_runtime *p_rt;
+ struct sdw_bus *bus = m_rt->bus;
+
+ 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, bus->params.col, 0, 0, 1,
+ bus->params.col - 1, SDW_BLK_PKG_PER_PORT, 0x0);
+
+ sdw_fill_port_params(&p_rt->port_params, p_rt->num, bus->params.col - 1,
+ SDW_PORT_FLOW_MODE_ISOCH, SDW_PORT_DATA_MODE_NORMAL);
+ }
+}
+
+static void sdw_compute_dp0_port_params(struct sdw_bus *bus)
+{
+ struct sdw_master_runtime *m_rt;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ sdw_compute_dp0_master_ports(m_rt);
+ sdw_compute_dp0_slave_ports(m_rt);
+ }
+}
+
+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) {
+ if (p_rt->lane != params->lane)
+ continue;
+
+ 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, p_rt->lane);
+
+ 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;
+ }
+
+ t_data.lane = params->lane;
+ 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 port_bo, i, l;
+ int hstop;
+
+ /* Run loop for all groups to compute transport parameters */
+ for (l = 0; l < SDW_MAX_LANES; l++) {
+ if (l > 0 && !bus->lane_used_bandwidth[l])
+ continue;
+ /* reset hstop for each lane */
+ hstop = bus->params.col - 1;
+ for (i = 0; i < count; i++) {
+ if (params[i].lane != l)
+ continue;
+ port_bo = 1;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ /*
+ * Only runtimes with CONFIGURED, PREPARED, ENABLED, and DISABLED
+ * states should be included in the bandwidth calculation.
+ */
+ if (m_rt->stream->state > SDW_STREAM_DISABLED ||
+ m_rt->stream->state < SDW_STREAM_CONFIGURED)
+ continue;
+ sdw_compute_master_ports(m_rt, &params[i], &port_bo, hstop);
+ }
+
+ hstop = hstop - params[i].hwidth;
+ }
+ }
+}
+
+static int sdw_compute_group_params(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream,
+ struct sdw_group_params *params,
+ struct sdw_group *group)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_port_runtime *p_rt;
+ int sel_col = bus->params.col;
+ unsigned int rate, bps, ch;
+ int i, l, column_needed;
+
+ /* Calculate bandwidth per group */
+ for (i = 0; i < group->count; i++) {
+ params[i].rate = group->rates[i];
+ params[i].lane = group->lanes[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) {
+ if (m_rt->stream == stream) {
+ /* Only runtime during prepare should be added */
+ if (stream->state != SDW_STREAM_CONFIGURED)
+ continue;
+ } else {
+ /*
+ * Include runtimes with running (ENABLED/PREPARED state) and
+ * paused (DISABLED state) streams
+ */
+ if (m_rt->stream->state != SDW_STREAM_ENABLED &&
+ m_rt->stream->state != SDW_STREAM_PREPARED &&
+ m_rt->stream->state != SDW_STREAM_DISABLED)
+ continue;
+ }
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ rate = m_rt->stream->params.rate;
+ bps = m_rt->stream->params.bps;
+ ch = hweight32(p_rt->ch_mask);
+
+ for (i = 0; i < group->count; i++) {
+ if (rate == params[i].rate && p_rt->lane == params[i].lane)
+ params[i].payload_bw += bps * ch;
+ }
+ }
+ }
+
+ for (l = 0; l < SDW_MAX_LANES; l++) {
+ if (l > 0 && !bus->lane_used_bandwidth[l])
+ continue;
+ /* reset column_needed for each lane */
+ column_needed = 0;
+ for (i = 0; i < group->count; i++) {
+ if (params[i].lane != l)
+ continue;
+
+ params[i].hwidth = (sel_col * params[i].payload_bw +
+ params[i].full_bw - 1) / params[i].full_bw;
+
+ column_needed += params[i].hwidth;
+ /* There is no control column for lane 1 and above */
+ if (column_needed > sel_col)
+ return -EINVAL;
+ /* Column 0 is control column on lane 0 */
+ if (params[i].lane == 0 && column_needed > sel_col - 1)
+ return -EINVAL;
+ }
+ }
+
+
+ return 0;
+}
+
+static int sdw_add_element_group_count(struct sdw_group *group,
+ unsigned int rate, unsigned int lane)
+{
+ int num = group->count;
+ int i;
+
+ for (i = 0; i <= num; i++) {
+ if (rate == group->rates[i] && lane == group->lanes[i])
+ break;
+
+ if (i != num)
+ continue;
+
+ if (group->count >= group->max_size) {
+ unsigned int *rates;
+ unsigned int *lanes;
+
+ group->max_size += 1;
+ rates = krealloc(group->rates,
+ (sizeof(int) * group->max_size),
+ GFP_KERNEL);
+ if (!rates)
+ return -ENOMEM;
+
+ group->rates = rates;
+
+ lanes = krealloc(group->lanes,
+ (sizeof(int) * group->max_size),
+ GFP_KERNEL);
+ if (!lanes)
+ return -ENOMEM;
+
+ group->lanes = lanes;
+ }
+
+ group->rates[group->count] = rate;
+ group->lanes[group->count++] = lane;
+ }
+
+ return 0;
+}
+
+static int sdw_get_group_count(struct sdw_bus *bus,
+ struct sdw_group *group)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_port_runtime *p_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;
+
+ group->lanes = kcalloc(group->max_size, sizeof(int), GFP_KERNEL);
+ if (!group->lanes) {
+ kfree(group->rates);
+ group->rates = NULL;
+ return -ENOMEM;
+ }
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ if (m_rt->stream->state == SDW_STREAM_DEPREPARED)
+ continue;
+
+ 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;
+ }
+ /*
+ * Different ports could use different lane, add group element
+ * even if m_rt is the first entry
+ */
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ ret = sdw_add_element_group_count(group, rate, p_rt->lane);
+ if (ret < 0) {
+ kfree(group->rates);
+ kfree(group->lanes);
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_compute_port_params: Compute transport and port parameters
+ *
+ * @bus: SDW Bus instance
+ * @stream: Soundwire stream
+ */
+static int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
+{
+ 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, stream, params, &group);
+ if (ret < 0)
+ goto free_params;
+
+ _sdw_compute_port_params(bus, params, group.count);
+
+free_params:
+ kfree(params);
+out:
+ kfree(group.rates);
+ kfree(group.lanes);
+
+ return ret;
+}
+
+static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq)
+{
+ struct sdw_master_prop *prop = &bus->prop;
+ 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;
+
+ if (clk_freq * (sdw_cols[c] - 1) <
+ bus->params.bandwidth * sdw_cols[c])
+ continue;
+
+ bus->params.row = sdw_rows[r];
+ bus->params.col = sdw_cols[c];
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static bool is_clock_scaling_supported(struct sdw_bus *bus)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_slave_runtime *s_rt;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node)
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node)
+ if (!is_clock_scaling_supported_by_slave(s_rt->slave))
+ return false;
+
+ return true;
+}
+
+/**
+ * is_lane_connected_to_all_peripherals: Check if the given manager lane connects to all peripherals
+ * So that all peripherals can use the manager lane.
+ *
+ * @m_rt: Manager runtime
+ * @lane: Lane number
+ */
+static bool is_lane_connected_to_all_peripherals(struct sdw_master_runtime *m_rt, unsigned int lane)
+{
+ struct sdw_slave_prop *slave_prop;
+ struct sdw_slave_runtime *s_rt;
+ int i;
+
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ slave_prop = &s_rt->slave->prop;
+ for (i = 1; i < SDW_MAX_LANES; i++) {
+ if (slave_prop->lane_maps[i] == lane) {
+ dev_dbg(&s_rt->slave->dev,
+ "M lane %d is connected to P lane %d\n",
+ lane, i);
+ break;
+ }
+ }
+ if (i == SDW_MAX_LANES) {
+ dev_dbg(&s_rt->slave->dev, "M lane %d is not connected\n", lane);
+ return false;
+ }
+ }
+ return true;
+}
+
+static int get_manager_lane(struct sdw_bus *bus, struct sdw_master_runtime *m_rt,
+ struct sdw_slave_runtime *s_rt, unsigned int curr_dr_freq)
+{
+ struct sdw_slave_prop *slave_prop = &s_rt->slave->prop;
+ struct sdw_port_runtime *m_p_rt;
+ unsigned int required_bandwidth;
+ int m_lane;
+ int l;
+
+ for (l = 1; l < SDW_MAX_LANES; l++) {
+ if (!slave_prop->lane_maps[l])
+ continue;
+
+ required_bandwidth = 0;
+ list_for_each_entry(m_p_rt, &m_rt->port_list, port_node) {
+ required_bandwidth += m_rt->stream->params.rate *
+ hweight32(m_p_rt->ch_mask) *
+ m_rt->stream->params.bps;
+ }
+ if (required_bandwidth <=
+ curr_dr_freq - bus->lane_used_bandwidth[l]) {
+ /* Check if m_lane is connected to all Peripherals */
+ if (!is_lane_connected_to_all_peripherals(m_rt,
+ slave_prop->lane_maps[l])) {
+ dev_dbg(bus->dev,
+ "Not all Peripherals are connected to M lane %d\n",
+ slave_prop->lane_maps[l]);
+ continue;
+ }
+ m_lane = slave_prop->lane_maps[l];
+ dev_dbg(&s_rt->slave->dev, "M lane %d is used\n", m_lane);
+ bus->lane_used_bandwidth[l] += required_bandwidth;
+ /*
+ * Use non-zero manager lane, subtract the lane 0
+ * bandwidth that is already calculated
+ */
+ bus->params.bandwidth -= required_bandwidth;
+ return m_lane;
+ }
+ }
+
+ /* No available multi lane found, only lane 0 can be used */
+ return 0;
+}
+
+/**
+ * sdw_compute_bus_params: Compute bus parameters
+ *
+ * @bus: SDW Bus instance
+ */
+static int sdw_compute_bus_params(struct sdw_bus *bus)
+{
+ struct sdw_master_prop *mstr_prop = &bus->prop;
+ struct sdw_slave_prop *slave_prop;
+ struct sdw_port_runtime *m_p_rt;
+ struct sdw_port_runtime *s_p_rt;
+ struct sdw_master_runtime *m_rt;
+ struct sdw_slave_runtime *s_rt;
+ unsigned int curr_dr_freq = 0;
+ int i, l, clk_values, ret;
+ bool is_gear = false;
+ int m_lane = 0;
+ 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;
+ }
+
+ /* If dynamic scaling is not supported, don't try higher freq */
+ if (!is_clock_scaling_supported(bus))
+ clk_values = 1;
+
+ for (i = 0; i < clk_values; i++) {
+ if (!clk_buf)
+ curr_dr_freq = bus->params.max_dr_freq;
+ else
+ curr_dr_freq = (is_gear) ?
+ (bus->params.max_dr_freq >> clk_buf[i]) :
+ clk_buf[i] * SDW_DOUBLE_RATE_FACTOR;
+
+ if (curr_dr_freq * (mstr_prop->default_col - 1) >=
+ bus->params.bandwidth * mstr_prop->default_col)
+ break;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ /*
+ * Get the first s_rt that will be used to find the available lane that
+ * can be used. No need to check all Peripherals because we can't use
+ * multi-lane if we can't find any available lane for the first Peripheral.
+ */
+ s_rt = list_first_entry(&m_rt->slave_rt_list,
+ struct sdw_slave_runtime, m_rt_node);
+
+ /*
+ * Find the available Manager lane that connected to the first Peripheral.
+ */
+ m_lane = get_manager_lane(bus, m_rt, s_rt, curr_dr_freq);
+ if (m_lane > 0)
+ goto out;
+ }
+
+ /*
+ * 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;
+ }
+out:
+ /* multilane can be used */
+ if (m_lane > 0) {
+ /* Set Peripheral lanes */
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ slave_prop = &s_rt->slave->prop;
+ for (l = 1; l < SDW_MAX_LANES; l++) {
+ if (slave_prop->lane_maps[l] == m_lane) {
+ list_for_each_entry(s_p_rt, &s_rt->port_list, port_node) {
+ s_p_rt->lane = l;
+ dev_dbg(&s_rt->slave->dev,
+ "Set P lane %d for port %d\n",
+ l, s_p_rt->num);
+ }
+ break;
+ }
+ }
+ }
+ /*
+ * Set Manager lanes. Configure the last m_rt in bus->m_rt_list only since
+ * we don't want to touch other m_rts that are already working.
+ */
+ list_for_each_entry(m_p_rt, &m_rt->port_list, port_node) {
+ m_p_rt->lane = m_lane;
+ }
+ }
+
+ if (!mstr_prop->default_frame_rate || !mstr_prop->default_row)
+ return -EINVAL;
+
+ mstr_prop->default_col = curr_dr_freq / mstr_prop->default_frame_rate /
+ mstr_prop->default_row;
+
+ 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
+ * @stream: Soundwire stream
+ */
+int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
+{
+ int ret;
+
+ /* Computes clock frequency, frame shape and frame frequency */
+ ret = sdw_compute_bus_params(bus);
+ if (ret < 0)
+ return ret;
+
+ if (stream->type == SDW_STREAM_BPT) {
+ sdw_compute_dp0_port_params(bus);
+ return 0;
+ }
+
+ /* Compute transport and port params */
+ ret = sdw_compute_port_params(bus, stream);
+ 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
index fd8d034cfec1..9db78f3d7615 100644
--- a/drivers/soundwire/intel.c
+++ b/drivers/soundwire/intel.c
@@ -6,238 +6,541 @@
*/
#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
-#include <linux/interrupt.h>
-#include <linux/platform_device.h>
+#include <linux/io.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"
-/* 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)
-
-enum intel_pdi_type {
- INTEL_PDI_IN = 0,
- INTEL_PDI_OUT = 1,
- INTEL_PDI_BD = 2,
-};
+static int intel_wait_bit(void __iomem *base, int offset, u32 mask, u32 target)
+{
+ int timeout = 10;
+ u32 reg_read;
-struct sdw_intel {
- struct sdw_cdns cdns;
- int instance;
- struct sdw_intel_link_res *res;
-};
+ do {
+ reg_read = readl(base + offset);
+ if ((reg_read & mask) == target)
+ return 0;
-#define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns)
+ timeout--;
+ usleep_range(50, 100);
+ } while (timeout != 0);
-/*
- * Read, write helpers for HW registers
- */
-static inline int intel_readl(void __iomem *base, int offset)
+ return -EAGAIN;
+}
+
+static int intel_clear_bit(void __iomem *base, int offset, u32 value, u32 mask)
{
- return readl(base + offset);
+ writel(value, base + offset);
+ return intel_wait_bit(base, offset, mask, 0);
}
-static inline void intel_writel(void __iomem *base, int offset, int value)
+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);
}
-static inline u16 intel_readw(void __iomem *base, int offset)
+/*
+ * 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)
{
- return readw(base + offset);
+ 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 inline void intel_writew(void __iomem *base, int offset, u16 value)
+static int intel_reg_show(struct seq_file *s_file, void *data)
{
- writew(value, base + offset);
+ struct sdw_intel *sdw = s_file->private;
+ void __iomem *s = sdw->link_res->shim;
+ void __iomem *a = sdw->link_res->alh;
+ ssize_t ret;
+ int i, j;
+ unsigned int links, reg;
+
+ char *buf __free(kfree) = 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);
+
+ return 0;
}
+DEFINE_SHOW_ATTRIBUTE(intel_reg);
-static int intel_clear_bit(void __iomem *base, int offset, u32 value, u32 mask)
+static int intel_set_m_datamode(void *data, u64 value)
{
- int timeout = 10;
- u32 reg_read;
+ struct sdw_intel *sdw = data;
+ struct sdw_bus *bus = &sdw->cdns.bus;
- writel(value, base + offset);
- do {
- reg_read = readl(base + offset);
- if (!(reg_read & mask))
- return 0;
+ if (value > SDW_PORT_DATA_MODE_STATIC_1)
+ return -EINVAL;
- timeout--;
- udelay(50);
- } while (timeout != 0);
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
- return -EAGAIN;
+ 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_bit(void __iomem *base, int offset, u32 value, u32 mask)
+static int intel_set_s_datamode(void *data, u64 value)
{
- int timeout = 10;
- u32 reg_read;
+ struct sdw_intel *sdw = data;
+ struct sdw_bus *bus = &sdw->cdns.bus;
- writel(value, base + offset);
- do {
- reg_read = readl(base + offset);
- if (reg_read & mask)
- return 0;
+ if (value > SDW_PORT_DATA_MODE_STATIC_1)
+ return -EINVAL;
- timeout--;
- udelay(50);
- } while (timeout != 0);
+ /* Userspace changed the hardware state behind the kernel's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
- return -EAGAIN;
+ 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;
-static int intel_link_power_up(struct sdw_intel *sdw)
+ /* 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->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;
+ void __iomem *shim = sdw->link_res->shim;
+ u16 ioctl;
- sdw->cdns.link_up = true;
- return 0;
+ /* 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 */
}
-static int intel_shim_init(struct sdw_intel *sdw)
+/* this needs to be called with shim_lock */
+static void intel_shim_init(struct sdw_intel *sdw)
{
- void __iomem *shim = sdw->res->shim;
+ void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
- int sync_reg, ret;
- u16 ioctl = 0, act = 0;
+ u16 ioctl = 0, act;
/* 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);
- /* Switch to MIP from Glue logic */
- ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id));
+ intel_shim_glue_to_master_ip(sdw);
- ioctl &= ~(SDW_SHIM_IOCTL_DOE);
- intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ act = intel_readw(shim, SDW_SHIM_CTMCTL(link_id));
+ 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);
+}
- ioctl &= ~(SDW_SHIM_IOCTL_DO);
- intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+static int intel_shim_check_wake(struct sdw_intel *sdw)
+{
+ void __iomem *shim;
+ u16 wake_sts;
- ioctl |= (SDW_SHIM_IOCTL_MIF);
- intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+ shim = sdw->link_res->shim;
+ wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS);
- ioctl &= ~(SDW_SHIM_IOCTL_BKE);
- ioctl &= ~(SDW_SHIM_IOCTL_COE);
+ return wake_sts & BIT(sdw->instance);
+}
- intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl);
+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;
- 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);
+ 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 bool intel_check_cmdsync_unlocked(struct sdw_intel *sdw)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ int sync_reg;
- /* 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));
+ return !!(sync_reg & SDW_SHIM_SYNC_CMDSYNC_MASK);
+}
- /* 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);
+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 clock_source;
+ u32 syncprd;
+ u32 sync_reg;
+ bool lcap_mlcs;
+
+ 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+). On MeteorLake additional
+ * frequencies are available with the MLCS clock source selection.
+ */
+ lcap_mlcs = intel_readl(shim, SDW_SHIM_LCAP) & SDW_SHIM_LCAP_MLCS_MASK;
+
+ if (prop->mclk_freq % 6000000) {
+ if (prop->mclk_freq % 2400000) {
+ if (lcap_mlcs) {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24_576;
+ clock_source = SDW_SHIM_MLCS_CARDINAL_CLK;
+ } else {
+ dev_err(sdw->cdns.dev, "%s: invalid clock configuration, mclk %d lcap_mlcs %d\n",
+ __func__, prop->mclk_freq, lcap_mlcs);
+ ret = -EINVAL;
+ goto out;
+ }
+ } else {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4;
+ clock_source = SDW_SHIM_MLCS_XTAL_CLK;
+ }
+ } else {
+ if (lcap_mlcs) {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_96;
+ clock_source = SDW_SHIM_MLCS_AUDIO_PLL_CLK;
+ } else {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24;
+ clock_source = SDW_SHIM_MLCS_XTAL_CLK;
+ }
+ }
+
+ 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;
+ }
+
+ /* update link clock if needed */
+ if (lcap_mlcs) {
+ link_control = intel_readl(shim, SDW_SHIM_LCTL);
+ u32p_replace_bits(&link_control, clock_source, SDW_SHIM_LCTL_MLCS_MASK);
+ intel_writel(shim, SDW_SHIM_LCTL, link_control);
+ }
+ }
+
+ *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;
+
+ /* 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;
+
+ intel_writel(shim, SDW_SHIM_SYNC, sync_reg);
+
+ return 0;
+}
+
+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;
}
@@ -246,47 +549,40 @@ static int intel_shim_init(struct sdw_intel *sdw)
* PDI routines
*/
static void intel_pdi_init(struct sdw_intel *sdw,
- struct sdw_cdns_stream_config *config)
+ struct sdw_cdns_stream_config *config)
{
- void __iomem *shim = sdw->res->shim;
+ void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
- int pcm_cap, pdm_cap;
+ int pcm_cap;
/* PCM Stream Capability */
pcm_cap = intel_readw(shim, SDW_SHIM_PCMSCAP(link_id));
- config->pcm_bd = (pcm_cap & SDW_SHIM_PCMSCAP_BSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_BSS);
- config->pcm_in = (pcm_cap & SDW_SHIM_PCMSCAP_ISS) >>
- SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_ISS);
- config->pcm_out = (pcm_cap & SDW_SHIM_PCMSCAP_OSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_OSS);
-
- /* PDM Stream Capability */
- pdm_cap = intel_readw(shim, SDW_SHIM_PDMSCAP(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);
- config->pdm_bd = (pdm_cap & SDW_SHIM_PDMSCAP_BSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_BSS);
- config->pdm_in = (pdm_cap & SDW_SHIM_PDMSCAP_ISS) >>
- SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_ISS);
- config->pdm_out = (pdm_cap & SDW_SHIM_PDMSCAP_OSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_OSS);
+ 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, bool pcm)
+intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num)
{
- void __iomem *shim = sdw->res->shim;
+ void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
int count;
- if (pcm) {
- count = intel_readw(shim, SDW_SHIM_PCMSYCHC(link_id, pdi_num));
- } else {
- count = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id));
- count = ((count & SDW_SHIM_PDMSCAP_CPSS) >>
- SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_CPSS));
- }
+ 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++;
@@ -295,14 +591,14 @@ intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm)
}
static int intel_pdi_get_ch_update(struct sdw_intel *sdw,
- struct sdw_cdns_pdi *pdi,
- unsigned int num_pdi,
- unsigned int *num_ch, bool pcm)
+ 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, pcm);
+ pdi->ch_count = intel_pdi_get_ch_cap(sdw, pdi->num);
ch_count += pdi->ch_count;
pdi++;
}
@@ -312,25 +608,16 @@ static int intel_pdi_get_ch_update(struct sdw_intel *sdw,
}
static int intel_pdi_stream_ch_update(struct sdw_intel *sdw,
- struct sdw_cdns_streams *stream, bool pcm)
+ struct sdw_cdns_streams *stream)
{
intel_pdi_get_ch_update(sdw, stream->bd, stream->num_bd,
- &stream->num_ch_bd, pcm);
+ &stream->num_ch_bd);
intel_pdi_get_ch_update(sdw, stream->in, stream->num_in,
- &stream->num_ch_in, pcm);
+ &stream->num_ch_in);
intel_pdi_get_ch_update(sdw, stream->out, stream->num_out,
- &stream->num_ch_out, pcm);
-
- return 0;
-}
-
-static int intel_pdi_ch_update(struct sdw_intel *sdw)
-{
- /* First update PCM streams followed by PDM streams */
- intel_pdi_stream_ch_update(sdw, &sdw->cdns.pcm, true);
- intel_pdi_stream_ch_update(sdw, &sdw->cdns.pdm, false);
+ &stream->num_ch_out);
return 0;
}
@@ -338,11 +625,14 @@ static int intel_pdi_ch_update(struct sdw_intel *sdw)
static void
intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
{
- void __iomem *shim = sdw->res->shim;
+ void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
int pdi_conf = 0;
- pdi->intel_alh_id = (link_id * 16) + pdi->num + 5;
+ /* 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
@@ -356,10 +646,9 @@ intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
else
pdi_conf &= ~(SDW_SHIM_PCMSYCM_DIR);
- pdi_conf |= (pdi->intel_alh_id <<
- SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_STREAM));
- pdi_conf |= (pdi->l_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_LCHN));
- pdi_conf |= (pdi->h_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_HCHN));
+ u32p_replace_bits(&pdi_conf, pdi->intel_alh_id, SDW_SHIM_PCMSYCM_STREAM);
+ u32p_replace_bits(&pdi_conf, pdi->l_ch_num, SDW_SHIM_PCMSYCM_LCHN);
+ u32p_replace_bits(&pdi_conf, pdi->h_ch_num, SDW_SHIM_PCMSYCM_HCHN);
intel_writew(shim, SDW_SHIM_PCMSYCHM(link_id, pdi->num), pdi_conf);
}
@@ -367,365 +656,374 @@ intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
static void
intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
{
- void __iomem *alh = sdw->res->alh;
+ void __iomem *alh = sdw->link_res->alh;
unsigned int link_id = sdw->instance;
unsigned int conf;
- pdi->intel_alh_id = (link_id * 16) + pdi->num + 5;
+ /* 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));
- conf |= (SDW_ALH_STRMZCFG_DMAT_VAL <<
- SDW_REG_SHIFT(SDW_ALH_STRMZCFG_DMAT));
-
- conf |= ((pdi->ch_count - 1) <<
- SDW_REG_SHIFT(SDW_ALH_STRMZCFG_CHN));
+ u32p_replace_bits(&conf, SDW_ALH_STRMZCFG_DMAT_VAL, SDW_ALH_STRMZCFG_DMAT);
+ u32p_replace_bits(&conf, pdi->ch_count - 1, SDW_ALH_STRMZCFG_CHN);
intel_writel(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id), conf);
}
-static int intel_config_stream(struct sdw_intel *sdw,
- struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai,
- struct snd_pcm_hw_params *hw_params, int link_id)
+static int intel_params_stream(struct sdw_intel *sdw,
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai,
+ struct snd_pcm_hw_params *hw_params,
+ int link_id, int alh_stream_id)
{
- if (sdw->res->ops && sdw->res->ops->config_stream)
- return sdw->res->ops->config_stream(sdw->res->arg,
- substream, dai, hw_params, link_id);
-
+ struct sdw_intel_link_res *res = sdw->link_res;
+ struct sdw_intel_stream_params_data params_data;
+
+ params_data.substream = substream;
+ 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;
}
/*
- * bank switch routines
+ * DAI routines
*/
-static int intel_pre_bank_switch(struct sdw_bus *bus)
+static int intel_free_stream(struct sdw_intel *sdw,
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai,
+ int link_id)
{
- struct sdw_cdns *cdns = bus_to_cdns(bus);
- struct sdw_intel *sdw = cdns_to_intel(cdns);
- void __iomem *shim = sdw->res->shim;
- int sync_reg;
+ struct sdw_intel_link_res *res = sdw->link_res;
+ struct sdw_intel_stream_free_data free_data;
- /* Write to register only for multi-link */
- if (!bus->multi_link)
- return 0;
+ free_data.substream = substream;
+ free_data.dai = dai;
+ free_data.link_id = link_id;
- /* Read 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);
+ if (res->ops && res->ops->free_stream && res->dev)
+ return res->ops->free_stream(res->dev, &free_data);
return 0;
}
-static int intel_post_bank_switch(struct sdw_bus *bus)
+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 = bus_to_cdns(bus);
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
struct sdw_intel *sdw = cdns_to_intel(cdns);
- void __iomem *shim = sdw->res->shim;
- int sync_reg, ret;
+ struct sdw_cdns_dai_runtime *dai_runtime;
+ struct sdw_cdns_pdi *pdi;
+ struct sdw_stream_config sconfig;
+ int ch, dir;
+ int ret;
- /* Write to register only for multi-link */
- if (!bus->multi_link)
- return 0;
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime)
+ return -EIO;
- /* Read SYNC register */
- sync_reg = intel_readl(shim, SDW_SHIM_SYNC);
+ ch = params_channels(params);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dir = SDW_DATA_DIR_RX;
+ else
+ dir = SDW_DATA_DIR_TX;
- /*
- * 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))
- return 0;
+ pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id);
- /*
- * 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;
+ if (!pdi)
+ return -EINVAL;
- ret = intel_clear_bit(shim, SDW_SHIM_SYNC, sync_reg,
- SDW_SHIM_SYNC_SYNCGO);
- if (ret < 0)
- dev_err(sdw->cdns.dev, "Post bank switch failed: %d", ret);
+ /* 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);
- return ret;
-}
+ /* store pdi and hw_params, may be needed in prepare step */
+ dai_runtime->paused = false;
+ dai_runtime->suspended = false;
+ dai_runtime->pdi = pdi;
-/*
- * DAI routines
- */
-
-static struct sdw_cdns_port *intel_alloc_port(struct sdw_intel *sdw,
- u32 ch, u32 dir, bool pcm)
-{
- struct sdw_cdns *cdns = &sdw->cdns;
- struct sdw_cdns_port *port = NULL;
- int i, ret = 0;
+ /* Inform DSP about PDI stream number */
+ ret = intel_params_stream(sdw, substream, dai, params,
+ sdw->instance,
+ pdi->intel_alh_id);
+ if (ret)
+ return ret;
- for (i = 0; i < cdns->num_ports; i++) {
- if (cdns->ports[i].assigned == true)
- continue;
+ sconfig.direction = dir;
+ sconfig.ch_count = ch;
+ sconfig.frame_rate = params_rate(params);
+ sconfig.type = dai_runtime->stream_type;
- port = &cdns->ports[i];
- port->assigned = true;
- port->direction = dir;
- port->ch = ch;
- break;
- }
+ sconfig.bps = snd_pcm_format_width(params_format(params));
- if (!port) {
- dev_err(cdns->dev, "Unable to find a free port\n");
- return NULL;
- }
+ /* Port configuration */
+ struct sdw_port_config *pconfig __free(kfree) = kzalloc(sizeof(*pconfig),
+ GFP_KERNEL);
+ if (!pconfig)
+ return -ENOMEM;
- if (pcm) {
- ret = sdw_cdns_alloc_stream(cdns, &cdns->pcm, port, ch, dir);
- if (ret)
- goto out;
+ pconfig->num = pdi->num;
+ pconfig->ch_mask = (1 << ch) - 1;
- intel_pdi_shim_configure(sdw, port->pdi);
- sdw_cdns_config_stream(cdns, port, ch, dir, port->pdi);
+ 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);
- intel_pdi_alh_configure(sdw, port->pdi);
+ return ret;
+}
- } else {
- ret = sdw_cdns_alloc_stream(cdns, &cdns->pdm, port, ch, dir);
+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 = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime) {
+ dev_err(dai->dev, "failed to get dai runtime in %s\n",
+ __func__);
+ return -EIO;
}
-out:
- if (ret) {
- port->assigned = false;
- port = NULL;
- }
+ if (dai_runtime->suspended) {
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_pcm_hw_params *hw_params;
- return port;
-}
+ hw_params = &rtd->dpcm[substream->stream].hw_params;
-static void intel_port_cleanup(struct sdw_cdns_dma_data *dma)
-{
- int i;
+ dai_runtime->suspended = false;
- for (i = 0; i < dma->nr_ports; i++) {
- if (dma->port[i]) {
- dma->port[i]->pdi->assigned = false;
- dma->port[i]->pdi = NULL;
- dma->port[i]->assigned = false;
- dma->port[i] = NULL;
- }
+ /*
+ * .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, dai,
+ hw_params,
+ sdw->instance,
+ dai_runtime->pdi->intel_alh_id);
}
+
+ return ret;
}
-static int intel_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
+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_dma_data *dma;
- struct sdw_stream_config sconfig;
- struct sdw_port_config *pconfig;
- int ret, i, ch, dir;
- bool pcm = true;
+ struct sdw_cdns_dai_runtime *dai_runtime;
+ int ret;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ 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;
-
- if (dma->stream_type == SDW_STREAM_PDM) {
- /* TODO: Check whether PDM decimator is already in use */
- dma->nr_ports = sdw_cdns_get_stream(cdns, &cdns->pdm, ch, dir);
- pcm = false;
- } else {
- dma->nr_ports = sdw_cdns_get_stream(cdns, &cdns->pcm, ch, dir);
+ /*
+ * 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;
}
- if (!dma->nr_ports) {
- dev_err(dai->dev, "ports/resources not available");
- return -EINVAL;
+ ret = intel_free_stream(sdw, substream, dai, sdw->instance);
+ if (ret < 0) {
+ dev_err(dai->dev, "intel_free_stream: failed %d\n", ret);
+ return ret;
}
- dma->port = kcalloc(dma->nr_ports, sizeof(*dma->port), GFP_KERNEL);
- if (!dma->port)
- return -ENOMEM;
+ dai_runtime->pdi = NULL;
- for (i = 0; i < dma->nr_ports; i++) {
- dma->port[i] = intel_alloc_port(sdw, ch, dir, pcm);
- if (!dma->port[i]) {
- ret = -EINVAL;
- goto port_error;
- }
- }
+ return 0;
+}
- /* Inform DSP about PDI stream number */
- for (i = 0; i < dma->nr_ports; i++) {
- ret = intel_config_stream(sdw, substream, dai, params,
- dma->port[i]->pdi->intel_alh_id);
- if (ret)
- goto port_error;
- }
+static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, int direction)
+{
+ return cdns_set_sdw_stream(dai, stream, direction);
+}
- sconfig.direction = dir;
- sconfig.ch_count = ch;
- sconfig.frame_rate = params_rate(params);
- sconfig.type = dma->stream_type;
+static void *intel_get_sdw_stream(struct snd_soc_dai *dai,
+ int direction)
+{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_cdns_dai_runtime *dai_runtime;
- if (dma->stream_type == SDW_STREAM_PDM) {
- sconfig.frame_rate *= 50;
- sconfig.bps = 1;
- } else {
- sconfig.bps = snd_pcm_format_width(params_format(params));
- }
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime)
+ return ERR_PTR(-EINVAL);
- /* Port configuration */
- pconfig = kcalloc(dma->nr_ports, sizeof(*pconfig), GFP_KERNEL);
- if (!pconfig) {
- ret = -ENOMEM;
- goto port_error;
- }
+ return dai_runtime->stream;
+}
- for (i = 0; i < dma->nr_ports; i++) {
- pconfig[i].num = dma->port[i]->num;
- pconfig[i].ch_mask = (1 << ch) - 1;
- }
+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_cdns_dai_runtime *dai_runtime;
+ int ret = 0;
- ret = sdw_stream_add_master(&cdns->bus, &sconfig,
- pconfig, dma->nr_ports, dma->stream);
- if (ret) {
- dev_err(cdns->dev, "add master to stream failed:%d", ret);
- goto stream_error;
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime) {
+ dev_err(dai->dev, "failed to get dai runtime in %s\n",
+ __func__);
+ return -EIO;
}
- kfree(pconfig);
- return ret;
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_SUSPEND:
-stream_error:
- kfree(pconfig);
-port_error:
- intel_port_cleanup(dma);
- kfree(dma->port);
- return ret;
-}
+ /*
+ * 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.
+ */
-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_cdns_dma_data *dma;
- int ret;
+ dai_runtime->suspended = true;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
- return -EIO;
+ break;
- ret = sdw_stream_remove_master(&cdns->bus, dma->stream);
- if (ret < 0)
- dev_err(dai->dev, "remove master from stream %s failed: %d",
- dma->stream->name, ret);
+ 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;
+ }
- intel_port_cleanup(dma);
- kfree(dma->port);
return ret;
}
-static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai,
- void *stream, int direction)
+static int intel_component_probe(struct snd_soc_component *component)
{
- return cdns_set_sdw_stream(dai, stream, true, direction);
+ 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_pdm_set_sdw_stream(struct snd_soc_dai *dai,
- void *stream, int direction)
+static int intel_component_dais_suspend(struct snd_soc_component *component)
{
- return cdns_set_sdw_stream(dai, stream, false, direction);
+ 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_cdns_dai_runtime *dai_runtime;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+
+ if (!dai_runtime)
+ continue;
+
+ if (dai_runtime->suspended)
+ continue;
+
+ if (dai_runtime->paused)
+ dai_runtime->suspended = true;
+ }
+
+ return 0;
}
static const struct snd_soc_dai_ops intel_pcm_dai_ops = {
.hw_params = intel_hw_params,
+ .prepare = intel_prepare,
.hw_free = intel_hw_free,
- .shutdown = sdw_cdns_shutdown,
- .set_sdw_stream = intel_pcm_set_sdw_stream,
-};
-
-static const struct snd_soc_dai_ops intel_pdm_dai_ops = {
- .hw_params = intel_hw_params,
- .hw_free = intel_hw_free,
- .shutdown = sdw_cdns_shutdown,
- .set_sdw_stream = intel_pdm_set_sdw_stream,
+ .trigger = intel_trigger,
+ .set_stream = intel_pcm_set_sdw_stream,
+ .get_stream = intel_get_sdw_stream,
};
static const struct snd_soc_component_driver dai_component = {
- .name = "soundwire",
+ .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, bool pcm)
+ 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 = kasprintf(GFP_KERNEL, "SDW%d Pin%d",
- cdns->instance, 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.stream_name = kasprintf(GFP_KERNEL,
- "SDW%d Tx%d",
- cdns->instance, i);
- if (!dais[i].playback.stream_name) {
- kfree(dais[i].name);
- return -ENOMEM;
- }
-
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.stream_name = kasprintf(GFP_KERNEL,
- "SDW%d Rx%d",
- cdns->instance, i);
- if (!dais[i].capture.stream_name) {
- kfree(dais[i].name);
- kfree(dais[i].playback.stream_name);
- return -ENOMEM;
- }
-
- dais[i].playback.channels_min = 1;
- dais[i].playback.channels_max = max_ch;
- dais[i].capture.rates = SNDRV_PCM_RATE_48000;
- dais[i].capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ dais[i].capture.channels_min = 1;
+ dais[i].capture.channels_max = max_ch;
}
- dais[i].id = SDW_DAI_ID_RANGE_START + i;
-
- if (pcm)
- dais[i].ops = &intel_pcm_dai_ops;
- else
- dais[i].ops = &intel_pdm_dai_ops;
+ dais[i].ops = &intel_pcm_dai_ops;
}
return 0;
@@ -733,13 +1031,30 @@ static int intel_create_dai(struct sdw_cdns *cdns,
static int intel_register_dai(struct sdw_intel *sdw)
{
+ struct sdw_cdns_dai_runtime **dai_runtime_array;
+ 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_stream_ch_update(sdw, &sdw->cdns.pcm);
+
/* DAIs are created based on total number of PDIs supported */
- num_dai = cdns->pcm.num_pdi + cdns->pdm.num_pdi;
+ num_dai = cdns->pcm.num_pdi;
+
+ dai_runtime_array = devm_kcalloc(cdns->dev, num_dai,
+ sizeof(struct sdw_cdns_dai_runtime *),
+ GFP_KERNEL);
+ if (!dai_runtime_array)
+ return -ENOMEM;
+ cdns->dai_runtime_array = dai_runtime_array;
dais = devm_kcalloc(cdns->dev, num_dai, sizeof(*dais), GFP_KERNEL);
if (!dais)
@@ -748,181 +1063,52 @@ static int intel_register_dai(struct sdw_intel *sdw)
/* Create PCM DAIs */
stream = &cdns->pcm;
- ret = intel_create_dai(cdns, dais, INTEL_PDI_IN,
- stream->num_in, off, stream->num_ch_in, true);
+ 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, true);
+ 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, true);
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_BD, cdns->pcm.num_bd,
+ off, stream->num_ch_bd);
if (ret)
return ret;
- /* Create PDM DAIs */
- stream = &cdns->pdm;
- off += cdns->pcm.num_bd;
- ret = intel_create_dai(cdns, dais, INTEL_PDI_IN,
- cdns->pdm.num_in, off, stream->num_ch_in, false);
- if (ret)
- return ret;
+ return devm_snd_soc_register_component(cdns->dev, &dai_component,
+ dais, num_dai);
+}
- off += cdns->pdm.num_in;
- ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT,
- cdns->pdm.num_out, off, stream->num_ch_out, false);
- if (ret)
- return ret;
- off += cdns->pdm.num_bd;
- ret = intel_create_dai(cdns, dais, INTEL_PDI_BD,
- cdns->pdm.num_bd, off, stream->num_ch_bd, false);
- if (ret)
- return ret;
+const struct sdw_intel_hw_ops sdw_intel_cnl_hw_ops = {
+ .debugfs_init = intel_debugfs_init,
+ .debugfs_exit = intel_debugfs_exit,
- return snd_soc_register_component(cdns->dev, &dai_component,
- dais, num_dai);
-}
+ .register_dai = intel_register_dai,
-static int intel_prop_read(struct sdw_bus *bus)
-{
- /* Initialize with default handler to read all DisCo properties */
- sdw_master_read_prop(bus);
+ .check_clock_stop = intel_check_clock_stop,
+ .start_bus = intel_start_bus,
+ .start_bus_after_reset = intel_start_bus_after_reset,
+ .start_bus_after_clock_stop = intel_start_bus_after_clock_stop,
+ .stop_bus = intel_stop_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;
+ .link_power_up = intel_link_power_up,
+ .link_power_down = intel_link_power_down,
- bus->prop.freq[0] = bus->prop.max_freq;
- bus->prop.err_threshold = 5;
+ .shim_check_wake = intel_shim_check_wake,
+ .shim_wake = intel_shim_wake,
- return 0;
-}
-
-static struct sdw_master_ops sdw_intel_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,
- .set_bus_conf = cdns_bus_conf,
.pre_bank_switch = intel_pre_bank_switch,
.post_bank_switch = intel_post_bank_switch,
-};
-
-/*
- * probe and init
- */
-static int intel_probe(struct platform_device *pdev)
-{
- struct sdw_cdns_stream_config config;
- 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_intel_ops.read_prop = intel_prop_read;
- sdw->cdns.bus.ops = &sdw_intel_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);
-
- /* Read the PDI config and initialize cadence PDI */
- intel_pdi_init(sdw, &config);
- ret = sdw_cdns_pdi_init(&sdw->cdns, config);
- if (ret)
- goto err_init;
- intel_pdi_ch_update(sdw);
-
- /* 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;
- }
-
- /* Register DAIs */
- ret = intel_register_dai(sdw);
- if (ret) {
- dev_err(sdw->cdns.dev, "DAI registration failed: %d", ret);
- snd_soc_unregister_component(sdw->cdns.dev);
- goto err_dai;
- }
-
- return 0;
-
-err_dai:
- free_irq(sdw->res->irq, sdw);
-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);
- snd_soc_unregister_component(sdw->cdns.dev);
- 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",
-
- },
+ .sync_arm = intel_shim_sync_arm,
+ .sync_go_unlocked = intel_shim_sync_go_unlocked,
+ .sync_go = intel_shim_sync_go,
+ .sync_check_cmdsync_unlocked = intel_check_cmdsync_unlocked,
};
-
-module_platform_driver(sdw_intel_drv);
-
-MODULE_LICENSE("Dual BSD/GPL");
-MODULE_ALIAS("platform:int-sdw");
-MODULE_DESCRIPTION("Intel Soundwire Master Driver");
+EXPORT_SYMBOL_NS(sdw_intel_cnl_hw_ops, "SOUNDWIRE_INTEL");
diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h
index c1a5bac6212e..86abc465260f 100644
--- a/drivers/soundwire/intel.h
+++ b/drivers/soundwire/intel.h
@@ -1,27 +1,270 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
-// Copyright(c) 2015-17 Intel Corporation.
+/* 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 hdac_bus;
+
/**
- * struct sdw_intel_res - Soundwire link resources
+ * struct sdw_intel_link_res - Soundwire Intel link resource structure,
+ * typically populated by the controller driver.
+ * @hw_ops: platform-specific ops
+ * @mmio_base: mmio base of SoundWire registers
* @registers: Link IO registers base
+ * @ip_offset: offset for MCP_IP registers
* @shim: Audio shim pointer
+ * @shim_vs: Audio vendor-specific shim pointer
* @alh: ALH (Audio Link Hub) pointer
* @irq: Interrupt line
* @ops: Shim callback ops
- * @arg: Shim callback ops argument
- *
- * This is set as pdata for each link instance.
+ * @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
+ * @mic_privacy: ACE version supports microphone privacy
+ * @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
+ * @hbus: hdac_bus pointer, needed for power management
*/
struct sdw_intel_link_res {
+ const struct sdw_intel_hw_ops *hw_ops;
+
+ void __iomem *mmio_base; /* not strictly needed, useful for debug */
void __iomem *registers;
+ u32 ip_offset;
void __iomem *shim;
+ void __iomem *shim_vs;
void __iomem *alh;
int irq;
const struct sdw_intel_ops *ops;
- void *arg;
+ struct device *dev;
+ struct mutex *shim_lock; /* protect shared registers */
+ u32 *shim_mask;
+ u32 clock_stop_quirks;
+ bool mic_privacy;
+ u32 link_mask;
+ struct sdw_cdns *cdns;
+ struct list_head list;
+ struct hdac_bus *hbus;
+};
+
+/**
+ * struct sdw_intel_bpt - SoundWire Intel BPT context
+ * @bpt_tx_stream: BPT TX stream
+ * @dmab_tx_bdl: BPT TX buffer descriptor list
+ * @bpt_rx_stream: BPT RX stream
+ * @dmab_rx_bdl: BPT RX buffer descriptor list
+ * @pdi0_buffer_size: PDI0 buffer size
+ * @pdi1_buffer_size: PDI1 buffer size
+ * @num_frames: number of frames
+ * @data_per_frame: data per frame
+ */
+struct sdw_intel_bpt {
+ struct hdac_ext_stream *bpt_tx_stream;
+ struct snd_dma_buffer dmab_tx_bdl;
+ struct hdac_ext_stream *bpt_rx_stream;
+ struct snd_dma_buffer dmab_rx_bdl;
+ unsigned int pdi0_buffer_size;
+ unsigned int pdi1_buffer_size;
+ unsigned int num_frames;
+ unsigned int data_per_frame;
};
+struct sdw_intel {
+ struct sdw_cdns cdns;
+ int instance;
+ struct sdw_intel_link_res *link_res;
+ bool startup_done;
+ struct sdw_intel_bpt bpt_ctx;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs;
+#endif
+};
+
+struct sdw_intel_prop {
+ u16 clde;
+ u16 doaise2;
+ u16 dodse2;
+ u16 clds;
+ u16 clss;
+ u16 doaise;
+ u16 doais;
+ u16 dodse;
+ u16 dods;
+};
+
+enum intel_pdi_type {
+ INTEL_PDI_IN = 0,
+ INTEL_PDI_OUT = 1,
+ INTEL_PDI_BD = 2,
+};
+
+/*
+ * 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);
+}
+
+#define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns)
+
+#define INTEL_MASTER_RESET_ITERATIONS 10
+
+#define SDW_INTEL_DELAYED_ENUMERATION_MS 100
+
+#define SDW_INTEL_CHECK_OPS(sdw, cb) ((sdw) && (sdw)->link_res && (sdw)->link_res->hw_ops && \
+ (sdw)->link_res->hw_ops->cb)
+#define SDW_INTEL_OPS(sdw, cb) ((sdw)->link_res->hw_ops->cb)
+
+#ifdef CONFIG_DEBUG_FS
+void intel_ace2x_debugfs_init(struct sdw_intel *sdw);
+void intel_ace2x_debugfs_exit(struct sdw_intel *sdw);
+#else
+static inline void intel_ace2x_debugfs_init(struct sdw_intel *sdw) {}
+static inline void intel_ace2x_debugfs_exit(struct sdw_intel *sdw) {}
+#endif
+
+static inline void sdw_intel_debugfs_init(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, debugfs_init))
+ SDW_INTEL_OPS(sdw, debugfs_init)(sdw);
+}
+
+static inline void sdw_intel_debugfs_exit(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, debugfs_exit))
+ SDW_INTEL_OPS(sdw, debugfs_exit)(sdw);
+}
+
+static inline int sdw_intel_register_dai(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, register_dai))
+ return SDW_INTEL_OPS(sdw, register_dai)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline void sdw_intel_check_clock_stop(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, check_clock_stop))
+ SDW_INTEL_OPS(sdw, check_clock_stop)(sdw);
+}
+
+static inline int sdw_intel_start_bus(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, start_bus))
+ return SDW_INTEL_OPS(sdw, start_bus)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline int sdw_intel_start_bus_after_reset(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, start_bus_after_reset))
+ return SDW_INTEL_OPS(sdw, start_bus_after_reset)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline int sdw_intel_start_bus_after_clock_stop(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, start_bus_after_clock_stop))
+ return SDW_INTEL_OPS(sdw, start_bus_after_clock_stop)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline int sdw_intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, stop_bus))
+ return SDW_INTEL_OPS(sdw, stop_bus)(sdw, clock_stop);
+ return -ENOTSUPP;
+}
+
+static inline int sdw_intel_link_power_up(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, link_power_up))
+ return SDW_INTEL_OPS(sdw, link_power_up)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline int sdw_intel_link_power_down(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, link_power_down))
+ return SDW_INTEL_OPS(sdw, link_power_down)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline int sdw_intel_shim_check_wake(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, shim_check_wake))
+ return SDW_INTEL_OPS(sdw, shim_check_wake)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline void sdw_intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, shim_wake))
+ SDW_INTEL_OPS(sdw, shim_wake)(sdw, wake_enable);
+}
+
+static inline void sdw_intel_sync_arm(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, sync_arm))
+ SDW_INTEL_OPS(sdw, sync_arm)(sdw);
+}
+
+static inline int sdw_intel_sync_go_unlocked(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, sync_go_unlocked))
+ return SDW_INTEL_OPS(sdw, sync_go_unlocked)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline int sdw_intel_sync_go(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, sync_go))
+ return SDW_INTEL_OPS(sdw, sync_go)(sdw);
+ return -ENOTSUPP;
+}
+
+static inline bool sdw_intel_sync_check_cmdsync_unlocked(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, sync_check_cmdsync_unlocked))
+ return SDW_INTEL_OPS(sdw, sync_check_cmdsync_unlocked)(sdw);
+ return false;
+}
+
+static inline int sdw_intel_get_link_count(struct sdw_intel *sdw)
+{
+ if (SDW_INTEL_CHECK_OPS(sdw, get_link_count))
+ return SDW_INTEL_OPS(sdw, get_link_count)(sdw);
+ return 4; /* default on older generations */
+}
+
+/* common bus management */
+int intel_start_bus(struct sdw_intel *sdw);
+int intel_start_bus_after_reset(struct sdw_intel *sdw);
+void intel_check_clock_stop(struct sdw_intel *sdw);
+int intel_start_bus_after_clock_stop(struct sdw_intel *sdw);
+int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop);
+
+/* common bank switch routines */
+int intel_pre_bank_switch(struct sdw_intel *sdw);
+int intel_post_bank_switch(struct sdw_intel *sdw);
+
#endif /* __SDW_INTEL_LOCAL_H */
diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c
new file mode 100644
index 000000000000..5d08364ad6d1
--- /dev/null
+++ b/drivers/soundwire/intel_ace2x.c
@@ -0,0 +1,1072 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+// Copyright(c) 2023 Intel Corporation
+
+/*
+ * Soundwire Intel ops for LunarLake
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
+#include <linux/string_choices.h>
+#include <sound/hdaudio.h>
+#include <sound/hda-mlink.h>
+#include <sound/hda-sdw-bpt.h>
+#include <sound/hda_register.h>
+#include <sound/pcm_params.h>
+#include "cadence_master.h"
+#include "bus.h"
+#include "intel.h"
+
+static int sdw_slave_bpt_stream_add(struct sdw_slave *slave, struct sdw_stream_runtime *stream)
+{
+ struct sdw_stream_config sconfig = {0};
+ struct sdw_port_config pconfig = {0};
+ int ret;
+
+ /* arbitrary configuration */
+ sconfig.frame_rate = 16000;
+ sconfig.ch_count = 1;
+ sconfig.bps = 32; /* this is required for BPT/BRA */
+ sconfig.direction = SDW_DATA_DIR_RX;
+ sconfig.type = SDW_STREAM_BPT;
+
+ pconfig.num = 0;
+ pconfig.ch_mask = BIT(0);
+
+ ret = sdw_stream_add_slave(slave, &sconfig, &pconfig, 1, stream);
+ if (ret)
+ dev_err(&slave->dev, "%s: failed: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int intel_ace2x_bpt_open_stream(struct sdw_intel *sdw, struct sdw_slave *slave,
+ struct sdw_bpt_msg *msg)
+{
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_bus *bus = &cdns->bus;
+ struct sdw_master_prop *prop = &bus->prop;
+ struct sdw_stream_runtime *stream;
+ struct sdw_stream_config sconfig;
+ struct sdw_port_config *pconfig;
+ unsigned int pdi0_buffer_size;
+ unsigned int tx_dma_bandwidth;
+ unsigned int pdi1_buffer_size;
+ unsigned int rx_dma_bandwidth;
+ unsigned int data_per_frame;
+ unsigned int tx_total_bytes;
+ struct sdw_cdns_pdi *pdi0;
+ struct sdw_cdns_pdi *pdi1;
+ unsigned int num_frames;
+ int command;
+ int ret1;
+ int ret;
+ int dir;
+ int i;
+
+ stream = sdw_alloc_stream("BPT", SDW_STREAM_BPT);
+ if (!stream)
+ return -ENOMEM;
+
+ cdns->bus.bpt_stream = stream;
+
+ ret = sdw_slave_bpt_stream_add(slave, stream);
+ if (ret < 0)
+ goto release_stream;
+
+ /* handle PDI0 first */
+ dir = SDW_DATA_DIR_TX;
+
+ pdi0 = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, 1, dir, 0);
+ if (!pdi0) {
+ dev_err(cdns->dev, "%s: sdw_cdns_alloc_pdi0 failed\n", __func__);
+ ret = -EINVAL;
+ goto remove_slave;
+ }
+
+ sdw_cdns_config_stream(cdns, 1, dir, pdi0);
+
+ /* handle PDI1 */
+ dir = SDW_DATA_DIR_RX;
+
+ pdi1 = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, 1, dir, 1);
+ if (!pdi1) {
+ dev_err(cdns->dev, "%s: sdw_cdns_alloc_pdi1 failed\n", __func__);
+ ret = -EINVAL;
+ goto remove_slave;
+ }
+
+ sdw_cdns_config_stream(cdns, 1, dir, pdi1);
+
+ /*
+ * the port config direction, number of channels and frame
+ * rate is totally arbitrary
+ */
+ sconfig.direction = dir;
+ sconfig.ch_count = 1;
+ sconfig.frame_rate = 16000;
+ sconfig.type = SDW_STREAM_BPT;
+ sconfig.bps = 32; /* this is required for BPT/BRA */
+
+ /* Port configuration */
+ pconfig = kcalloc(2, sizeof(*pconfig), GFP_KERNEL);
+ if (!pconfig) {
+ ret = -ENOMEM;
+ goto remove_slave;
+ }
+
+ for (i = 0; i < 2 /* num_pdi */; i++) {
+ pconfig[i].num = i;
+ pconfig[i].ch_mask = 1;
+ }
+
+ ret = sdw_stream_add_master(&cdns->bus, &sconfig, pconfig, 2, stream);
+ kfree(pconfig);
+
+ if (ret < 0) {
+ dev_err(cdns->dev, "add master to stream failed:%d\n", ret);
+ goto remove_slave;
+ }
+
+ ret = sdw_prepare_stream(cdns->bus.bpt_stream);
+ if (ret < 0)
+ goto remove_master;
+
+ command = (msg->flags & SDW_MSG_FLAG_WRITE) ? 0 : 1;
+
+ ret = sdw_cdns_bpt_find_buffer_sizes(command, cdns->bus.params.row, cdns->bus.params.col,
+ msg->len, SDW_BPT_MSG_MAX_BYTES, &data_per_frame,
+ &pdi0_buffer_size, &pdi1_buffer_size, &num_frames);
+ if (ret < 0)
+ goto deprepare_stream;
+
+ sdw->bpt_ctx.pdi0_buffer_size = pdi0_buffer_size;
+ sdw->bpt_ctx.pdi1_buffer_size = pdi1_buffer_size;
+ sdw->bpt_ctx.num_frames = num_frames;
+ sdw->bpt_ctx.data_per_frame = data_per_frame;
+ tx_dma_bandwidth = div_u64((u64)pdi0_buffer_size * 8 * (u64)prop->default_frame_rate,
+ num_frames);
+ rx_dma_bandwidth = div_u64((u64)pdi1_buffer_size * 8 * (u64)prop->default_frame_rate,
+ num_frames);
+
+ dev_dbg(cdns->dev, "Message len %d transferred in %d frames (%d per frame)\n",
+ msg->len, num_frames, data_per_frame);
+ dev_dbg(cdns->dev, "sizes pdi0 %d pdi1 %d tx_bandwidth %d rx_bandwidth %d\n",
+ pdi0_buffer_size, pdi1_buffer_size, tx_dma_bandwidth, rx_dma_bandwidth);
+
+ ret = hda_sdw_bpt_open(cdns->dev->parent, /* PCI device */
+ sdw->instance, &sdw->bpt_ctx.bpt_tx_stream,
+ &sdw->bpt_ctx.dmab_tx_bdl, pdi0_buffer_size, tx_dma_bandwidth,
+ &sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl,
+ pdi1_buffer_size, rx_dma_bandwidth);
+ if (ret < 0) {
+ dev_err(cdns->dev, "%s: hda_sdw_bpt_open failed %d\n", __func__, ret);
+ goto deprepare_stream;
+ }
+
+ if (!command) {
+ ret = sdw_cdns_prepare_write_dma_buffer(msg->dev_num, msg->addr, msg->buf,
+ msg->len, data_per_frame,
+ sdw->bpt_ctx.dmab_tx_bdl.area,
+ pdi0_buffer_size, &tx_total_bytes);
+ } else {
+ ret = sdw_cdns_prepare_read_dma_buffer(msg->dev_num, msg->addr, msg->len,
+ data_per_frame,
+ sdw->bpt_ctx.dmab_tx_bdl.area,
+ pdi0_buffer_size, &tx_total_bytes);
+ }
+
+ if (!ret)
+ return 0;
+
+ dev_err(cdns->dev, "%s: sdw_prepare_%s_dma_buffer failed %d\n",
+ __func__, str_read_write(command), ret);
+
+ ret1 = hda_sdw_bpt_close(cdns->dev->parent, /* PCI device */
+ sdw->bpt_ctx.bpt_tx_stream, &sdw->bpt_ctx.dmab_tx_bdl,
+ sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl);
+ if (ret1 < 0)
+ dev_err(cdns->dev, "%s: hda_sdw_bpt_close failed: ret %d\n",
+ __func__, ret1);
+
+deprepare_stream:
+ sdw_deprepare_stream(cdns->bus.bpt_stream);
+
+remove_master:
+ ret1 = sdw_stream_remove_master(&cdns->bus, cdns->bus.bpt_stream);
+ if (ret1 < 0)
+ dev_err(cdns->dev, "%s: remove master failed: %d\n",
+ __func__, ret1);
+
+remove_slave:
+ ret1 = sdw_stream_remove_slave(slave, cdns->bus.bpt_stream);
+ if (ret1 < 0)
+ dev_err(cdns->dev, "%s: remove slave failed: %d\n",
+ __func__, ret1);
+
+release_stream:
+ sdw_release_stream(cdns->bus.bpt_stream);
+ cdns->bus.bpt_stream = NULL;
+
+ return ret;
+}
+
+static void intel_ace2x_bpt_close_stream(struct sdw_intel *sdw, struct sdw_slave *slave,
+ struct sdw_bpt_msg *msg)
+{
+ struct sdw_cdns *cdns = &sdw->cdns;
+ int ret;
+
+ ret = hda_sdw_bpt_close(cdns->dev->parent /* PCI device */, sdw->bpt_ctx.bpt_tx_stream,
+ &sdw->bpt_ctx.dmab_tx_bdl, sdw->bpt_ctx.bpt_rx_stream,
+ &sdw->bpt_ctx.dmab_rx_bdl);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: hda_sdw_bpt_close failed: ret %d\n",
+ __func__, ret);
+
+ ret = sdw_deprepare_stream(cdns->bus.bpt_stream);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: sdw_deprepare_stream failed: ret %d\n",
+ __func__, ret);
+
+ ret = sdw_stream_remove_master(&cdns->bus, cdns->bus.bpt_stream);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: remove master failed: %d\n",
+ __func__, ret);
+
+ ret = sdw_stream_remove_slave(slave, cdns->bus.bpt_stream);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: remove slave failed: %d\n",
+ __func__, ret);
+
+ cdns->bus.bpt_stream = NULL;
+}
+
+#define INTEL_BPT_MSG_BYTE_MIN 16
+
+static int intel_ace2x_bpt_send_async(struct sdw_intel *sdw, struct sdw_slave *slave,
+ struct sdw_bpt_msg *msg)
+{
+ struct sdw_cdns *cdns = &sdw->cdns;
+ int ret;
+
+ if (msg->len < INTEL_BPT_MSG_BYTE_MIN) {
+ dev_err(cdns->dev, "BPT message length %d is less than the minimum bytes %d\n",
+ msg->len, INTEL_BPT_MSG_BYTE_MIN);
+ return -EINVAL;
+ }
+
+ dev_dbg(cdns->dev, "BPT Transfer start\n");
+
+ ret = intel_ace2x_bpt_open_stream(sdw, slave, msg);
+ if (ret < 0)
+ return ret;
+
+ ret = hda_sdw_bpt_send_async(cdns->dev->parent, /* PCI device */
+ sdw->bpt_ctx.bpt_tx_stream, sdw->bpt_ctx.bpt_rx_stream);
+ if (ret < 0) {
+ dev_err(cdns->dev, "%s: hda_sdw_bpt_send_async failed: %d\n",
+ __func__, ret);
+
+ intel_ace2x_bpt_close_stream(sdw, slave, msg);
+
+ return ret;
+ }
+
+ ret = sdw_enable_stream(cdns->bus.bpt_stream);
+ if (ret < 0) {
+ dev_err(cdns->dev, "%s: sdw_stream_enable failed: %d\n",
+ __func__, ret);
+ intel_ace2x_bpt_close_stream(sdw, slave, msg);
+ }
+
+ return ret;
+}
+
+static int intel_ace2x_bpt_wait(struct sdw_intel *sdw, struct sdw_slave *slave,
+ struct sdw_bpt_msg *msg)
+{
+ struct sdw_cdns *cdns = &sdw->cdns;
+ int ret;
+
+ dev_dbg(cdns->dev, "BPT Transfer wait\n");
+
+ ret = hda_sdw_bpt_wait(cdns->dev->parent, /* PCI device */
+ sdw->bpt_ctx.bpt_tx_stream, sdw->bpt_ctx.bpt_rx_stream);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: hda_sdw_bpt_wait failed: %d\n", __func__, ret);
+
+ ret = sdw_disable_stream(cdns->bus.bpt_stream);
+ if (ret < 0) {
+ dev_err(cdns->dev, "%s: sdw_stream_enable failed: %d\n",
+ __func__, ret);
+ goto err;
+ }
+
+ if (msg->flags & SDW_MSG_FLAG_WRITE) {
+ ret = sdw_cdns_check_write_response(cdns->dev, sdw->bpt_ctx.dmab_rx_bdl.area,
+ sdw->bpt_ctx.pdi1_buffer_size,
+ sdw->bpt_ctx.num_frames);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: BPT Write failed %d\n", __func__, ret);
+ } else {
+ ret = sdw_cdns_check_read_response(cdns->dev, sdw->bpt_ctx.dmab_rx_bdl.area,
+ sdw->bpt_ctx.pdi1_buffer_size,
+ msg->buf, msg->len, sdw->bpt_ctx.num_frames,
+ sdw->bpt_ctx.data_per_frame);
+ if (ret < 0)
+ dev_err(cdns->dev, "%s: BPT Read failed %d\n", __func__, ret);
+ }
+
+err:
+ intel_ace2x_bpt_close_stream(sdw, slave, msg);
+
+ return ret;
+}
+
+/*
+ * shim vendor-specific (vs) ops
+ */
+
+static void intel_shim_vs_init(struct sdw_intel *sdw)
+{
+ void __iomem *shim_vs = sdw->link_res->shim_vs;
+ struct sdw_bus *bus = &sdw->cdns.bus;
+ struct sdw_intel_prop *intel_prop;
+ u16 clde;
+ u16 doaise2;
+ u16 dodse2;
+ u16 clds;
+ u16 clss;
+ u16 doaise;
+ u16 doais;
+ u16 dodse;
+ u16 dods;
+ u16 act;
+
+ intel_prop = bus->vendor_specific_prop;
+ clde = intel_prop->clde;
+ doaise2 = intel_prop->doaise2;
+ dodse2 = intel_prop->dodse2;
+ clds = intel_prop->clds;
+ clss = intel_prop->clss;
+ doaise = intel_prop->doaise;
+ doais = intel_prop->doais;
+ dodse = intel_prop->dodse;
+ dods = intel_prop->dods;
+
+ act = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL);
+ u16p_replace_bits(&act, clde, SDW_SHIM3_INTEL_VS_ACTMCTL_CLDE);
+ u16p_replace_bits(&act, doaise2, SDW_SHIM3_INTEL_VS_ACTMCTL_DOAISE2);
+ u16p_replace_bits(&act, dodse2, SDW_SHIM3_INTEL_VS_ACTMCTL_DODSE2);
+ u16p_replace_bits(&act, clds, SDW_SHIM3_INTEL_VS_ACTMCTL_CLDS);
+ u16p_replace_bits(&act, clss, SDW_SHIM3_INTEL_VS_ACTMCTL_CLSS);
+ u16p_replace_bits(&act, doaise, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAISE);
+ u16p_replace_bits(&act, doais, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAIS);
+ u16p_replace_bits(&act, dodse, SDW_SHIM2_INTEL_VS_ACTMCTL_DODSE);
+ u16p_replace_bits(&act, dods, SDW_SHIM2_INTEL_VS_ACTMCTL_DODS);
+ act |= SDW_SHIM2_INTEL_VS_ACTMCTL_DACTQE;
+ intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL, act);
+ usleep_range(10, 15);
+}
+
+static void intel_shim_vs_set_clock_source(struct sdw_intel *sdw, u32 source)
+{
+ void __iomem *shim_vs = sdw->link_res->shim_vs;
+ u32 val;
+
+ val = intel_readl(shim_vs, SDW_SHIM2_INTEL_VS_LVSCTL);
+
+ u32p_replace_bits(&val, source, SDW_SHIM2_INTEL_VS_LVSCTL_MLCS);
+
+ intel_writel(shim_vs, SDW_SHIM2_INTEL_VS_LVSCTL, val);
+
+ dev_dbg(sdw->cdns.dev, "clock source %d LVSCTL %#x\n", source, val);
+}
+
+static int intel_shim_check_wake(struct sdw_intel *sdw)
+{
+ /*
+ * We follow the HDaudio example and resume unconditionally
+ * without checking the WAKESTS bit for that specific link
+ */
+
+ return 1;
+}
+
+static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
+{
+ u16 lsdiid = 0;
+ u16 wake_en;
+ u16 wake_sts;
+ int ret;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ ret = hdac_bus_eml_sdw_get_lsdiid_unlocked(sdw->link_res->hbus, sdw->instance, &lsdiid);
+ if (ret < 0)
+ goto unlock;
+
+ wake_en = snd_hdac_chip_readw(sdw->link_res->hbus, WAKEEN);
+
+ if (wake_enable) {
+ /* Enable the wakeup */
+ wake_en |= lsdiid;
+
+ snd_hdac_chip_writew(sdw->link_res->hbus, WAKEEN, wake_en);
+ } else {
+ /* Disable the wake up interrupt */
+ wake_en &= ~lsdiid;
+ snd_hdac_chip_writew(sdw->link_res->hbus, WAKEEN, wake_en);
+
+ /* Clear wake status (W1C) */
+ wake_sts = snd_hdac_chip_readw(sdw->link_res->hbus, STATESTS);
+ wake_sts |= lsdiid;
+ snd_hdac_chip_writew(sdw->link_res->hbus, STATESTS, wake_sts);
+ }
+unlock:
+ mutex_unlock(sdw->link_res->shim_lock);
+}
+
+static int intel_link_power_up(struct sdw_intel *sdw)
+{
+ struct sdw_bus *bus = &sdw->cdns.bus;
+ struct sdw_master_prop *prop = &bus->prop;
+ u32 *shim_mask = sdw->link_res->shim_mask;
+ unsigned int link_id = sdw->instance;
+ u32 clock_source;
+ u32 syncprd;
+ int ret;
+
+ if (prop->mclk_freq % 6000000) {
+ if (prop->mclk_freq % 2400000) {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24_576;
+ clock_source = SDW_SHIM2_MLCS_CARDINAL_CLK;
+ } else {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4;
+ clock_source = SDW_SHIM2_MLCS_XTAL_CLK;
+ }
+ } else {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_96;
+ clock_source = SDW_SHIM2_MLCS_AUDIO_PLL_CLK;
+ }
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ ret = hdac_bus_eml_sdw_power_up_unlocked(sdw->link_res->hbus, link_id);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_up failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ intel_shim_vs_set_clock_source(sdw, clock_source);
+
+ if (!*shim_mask) {
+ /* we first need to program the SyncPRD/CPU registers */
+ dev_dbg(sdw->cdns.dev, "first link up, programming SYNCPRD\n");
+
+ ret = hdac_bus_eml_sdw_set_syncprd_unlocked(sdw->link_res->hbus, syncprd);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_set_syncprd failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ /* SYNCPU will change once link is active */
+ ret = hdac_bus_eml_sdw_wait_syncpu_unlocked(sdw->link_res->hbus);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_wait_syncpu failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ hdac_bus_eml_enable_interrupt_unlocked(sdw->link_res->hbus, true,
+ AZX_REG_ML_LEPTR_ID_SDW, true);
+ }
+
+ *shim_mask |= BIT(link_id);
+
+ sdw->cdns.link_up = true;
+
+ intel_shim_vs_init(sdw);
+
+out:
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ return ret;
+}
+
+static int intel_link_power_down(struct sdw_intel *sdw)
+{
+ u32 *shim_mask = sdw->link_res->shim_mask;
+ unsigned int link_id = sdw->instance;
+ int ret;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ sdw->cdns.link_up = false;
+
+ *shim_mask &= ~BIT(link_id);
+
+ if (!*shim_mask)
+ hdac_bus_eml_enable_interrupt_unlocked(sdw->link_res->hbus, true,
+ AZX_REG_ML_LEPTR_ID_SDW, false);
+
+ ret = hdac_bus_eml_sdw_power_down_unlocked(sdw->link_res->hbus, link_id);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_down failed: %d\n",
+ __func__, ret);
+
+ /*
+ * 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_sync_arm(struct sdw_intel *sdw)
+{
+ unsigned int link_id = sdw->instance;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ hdac_bus_eml_sdw_sync_arm_unlocked(sdw->link_res->hbus, link_id);
+
+ mutex_unlock(sdw->link_res->shim_lock);
+}
+
+static int intel_sync_go_unlocked(struct sdw_intel *sdw)
+{
+ int ret;
+
+ ret = hdac_bus_eml_sdw_sync_go_unlocked(sdw->link_res->hbus);
+ if (ret < 0)
+ dev_err(sdw->cdns.dev, "%s: SyncGO clear failed: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int intel_sync_go(struct sdw_intel *sdw)
+{
+ int ret;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ ret = intel_sync_go_unlocked(sdw);
+
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ return ret;
+}
+
+static bool intel_check_cmdsync_unlocked(struct sdw_intel *sdw)
+{
+ return hdac_bus_eml_sdw_check_cmdsync_unlocked(sdw->link_res->hbus);
+}
+
+/* DAI callbacks */
+static int intel_params_stream(struct sdw_intel *sdw,
+ struct snd_pcm_substream *substream,
+ 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.substream = substream;
+ 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,
+ struct snd_pcm_substream *substream,
+ 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.substream = substream;
+ 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;
+}
+
+/*
+ * DAI operations
+ */
+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;
+ int ch, dir;
+ int ret;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ 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)
+ return -EINVAL;
+
+ /* use same definitions for alh_id as previous generations */
+ pdi->intel_alh_id = (sdw->instance * 16) + pdi->num + 3;
+ if (pdi->num >= 2)
+ pdi->intel_alh_id += 2;
+
+ /* the SHIM will be configured in the callback functions */
+
+ sdw_cdns_config_stream(cdns, ch, dir, pdi);
+
+ /* store pdi and state, 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, dai, params,
+ sdw->instance,
+ pdi->intel_alh_id);
+ if (ret)
+ return ret;
+
+ 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 */
+ struct sdw_port_config *pconfig __free(kfree) = kzalloc(sizeof(*pconfig),
+ GFP_KERNEL);
+ if (!pconfig)
+ return -ENOMEM;
+
+ 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);
+
+ return ret;
+}
+
+static int intel_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ 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 snd_pcm_hw_params *hw_params;
+ int ch, dir;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime) {
+ dev_err(dai->dev, "failed to get dai runtime in %s\n",
+ __func__);
+ return -EIO;
+ }
+
+ hw_params = &rtd->dpcm[substream->stream].hw_params;
+ if (dai_runtime->suspended) {
+ 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;
+
+ /* the SHIM will be configured in the callback functions */
+
+ sdw_cdns_config_stream(cdns, ch, dir, dai_runtime->pdi);
+ }
+
+ /* Inform DSP about PDI stream number */
+ return intel_params_stream(sdw, substream, dai, hw_params, sdw->instance,
+ dai_runtime->pdi->intel_alh_id);
+}
+
+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 = cdns->dai_runtime_array[dai->id];
+ 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, 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 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 *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_cdns_dai_runtime *dai_runtime;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ 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 program HDaudio DMA and send required IPC to audio
+ * firmware.
+ */
+ if (res->ops && res->ops->trigger) {
+ ret = res->ops->trigger(substream, cmd, dai);
+ if (ret < 0)
+ return ret;
+ }
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ 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;
+
+ 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 const struct snd_soc_dai_ops intel_pcm_dai_ops = {
+ .hw_params = intel_hw_params,
+ .prepare = intel_prepare,
+ .hw_free = intel_hw_free,
+ .trigger = intel_trigger,
+ .set_stream = intel_pcm_set_sdw_stream,
+ .get_stream = intel_get_sdw_stream,
+};
+
+static const struct snd_soc_component_driver dai_component = {
+ .name = "soundwire",
+};
+
+/*
+ * PDI routines
+ */
+static void intel_pdi_init(struct sdw_intel *sdw,
+ struct sdw_cdns_stream_config *config)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ int pcm_cap;
+
+ /* PCM Stream Capability */
+ pcm_cap = intel_readw(shim, SDW_SHIM2_PCMSCAP);
+
+ config->pcm_bd = FIELD_GET(SDW_SHIM2_PCMSCAP_BSS, pcm_cap);
+ config->pcm_in = FIELD_GET(SDW_SHIM2_PCMSCAP_ISS, pcm_cap);
+ config->pcm_out = FIELD_GET(SDW_SHIM2_PCMSCAP_ISS, 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;
+
+ /* zero based values for channel count in register */
+ return intel_readw(shim, SDW_SHIM2_PCMSYCHC(pdi_num)) + 1;
+}
+
+static void intel_pdi_get_ch_update(struct sdw_intel *sdw,
+ struct sdw_cdns_pdi *pdi,
+ unsigned int num_pdi,
+ unsigned int *num_ch)
+{
+ int ch_count = 0;
+ int i;
+
+ 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;
+}
+
+static void 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);
+}
+
+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)
+ return 0;
+
+ 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;
+ }
+
+ if (type == INTEL_PDI_BD || type == INTEL_PDI_IN) {
+ dais[i].capture.channels_min = 1;
+ dais[i].capture.channels_max = max_ch;
+ }
+
+ dais[i].ops = &intel_pcm_dai_ops;
+ }
+
+ return 0;
+}
+
+static int intel_register_dai(struct sdw_intel *sdw)
+{
+ struct sdw_cdns_dai_runtime **dai_runtime_array;
+ 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;
+ int ret;
+ int 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_stream_ch_update(sdw, &sdw->cdns.pcm);
+
+ /* DAIs are created based on total number of PDIs supported */
+ num_dai = cdns->pcm.num_pdi;
+
+ dai_runtime_array = devm_kcalloc(cdns->dev, num_dai,
+ sizeof(struct sdw_cdns_dai_runtime *),
+ GFP_KERNEL);
+ if (!dai_runtime_array)
+ return -ENOMEM;
+ cdns->dai_runtime_array = dai_runtime_array;
+
+ 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 void intel_program_sdi(struct sdw_intel *sdw, int dev_num)
+{
+ int ret;
+
+ ret = hdac_bus_eml_sdw_set_lsdiid(sdw->link_res->hbus, sdw->instance, dev_num);
+ if (ret < 0)
+ dev_err(sdw->cdns.dev, "%s: could not set lsdiid for link %d %d\n",
+ __func__, sdw->instance, dev_num);
+}
+
+static int intel_get_link_count(struct sdw_intel *sdw)
+{
+ int ret;
+
+ ret = hdac_bus_eml_get_count(sdw->link_res->hbus, true, AZX_REG_ML_LEPTR_ID_SDW);
+ if (!ret) {
+ dev_err(sdw->cdns.dev, "%s: could not retrieve link count\n", __func__);
+ return -ENODEV;
+ }
+
+ if (ret > SDW_INTEL_MAX_LINKS) {
+ dev_err(sdw->cdns.dev, "%s: link count %d exceed max %d\n", __func__, ret, SDW_INTEL_MAX_LINKS);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+const struct sdw_intel_hw_ops sdw_intel_lnl_hw_ops = {
+ .debugfs_init = intel_ace2x_debugfs_init,
+ .debugfs_exit = intel_ace2x_debugfs_exit,
+
+ .get_link_count = intel_get_link_count,
+
+ .register_dai = intel_register_dai,
+
+ .check_clock_stop = intel_check_clock_stop,
+ .start_bus = intel_start_bus,
+ .start_bus_after_reset = intel_start_bus_after_reset,
+ .start_bus_after_clock_stop = intel_start_bus_after_clock_stop,
+ .stop_bus = intel_stop_bus,
+
+ .link_power_up = intel_link_power_up,
+ .link_power_down = intel_link_power_down,
+
+ .shim_check_wake = intel_shim_check_wake,
+ .shim_wake = intel_shim_wake,
+
+ .pre_bank_switch = intel_pre_bank_switch,
+ .post_bank_switch = intel_post_bank_switch,
+
+ .sync_arm = intel_sync_arm,
+ .sync_go_unlocked = intel_sync_go_unlocked,
+ .sync_go = intel_sync_go,
+ .sync_check_cmdsync_unlocked = intel_check_cmdsync_unlocked,
+
+ .program_sdi = intel_program_sdi,
+
+ .bpt_send_async = intel_ace2x_bpt_send_async,
+ .bpt_wait = intel_ace2x_bpt_wait,
+};
+EXPORT_SYMBOL_NS(sdw_intel_lnl_hw_ops, "SOUNDWIRE_INTEL");
+
+MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
+MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_SDW_BPT");
diff --git a/drivers/soundwire/intel_ace2x_debugfs.c b/drivers/soundwire/intel_ace2x_debugfs.c
new file mode 100644
index 000000000000..fda8f0daaa96
--- /dev/null
+++ b/drivers/soundwire/intel_ace2x_debugfs.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2023 Intel Corporation
+
+#include <linux/acpi.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
+#include <linux/soundwire/sdw_registers.h>
+#include "bus.h"
+#include "cadence_master.h"
+#include "intel.h"
+
+/*
+ * 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 *vs_s = sdw->link_res->shim_vs;
+ ssize_t ret;
+ u32 pcm_cap;
+ int pcm_bd;
+ char *buf;
+ int 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, "\nShim\n");
+
+ ret += intel_sprintf(s, true, buf, ret, SDW_SHIM2_LECAP);
+ ret += intel_sprintf(s, false, buf, ret, SDW_SHIM2_PCMSCAP);
+
+ pcm_cap = intel_readw(s, SDW_SHIM2_PCMSCAP);
+ pcm_bd = FIELD_GET(SDW_SHIM2_PCMSCAP_BSS, pcm_cap);
+
+ for (j = 0; j < pcm_bd; j++) {
+ ret += intel_sprintf(s, false, buf, ret,
+ SDW_SHIM2_PCMSYCHM(j));
+ ret += intel_sprintf(s, false, buf, ret,
+ SDW_SHIM2_PCMSYCHC(j));
+ }
+
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nVS CLK controls\n");
+ ret += intel_sprintf(vs_s, true, buf, ret, SDW_SHIM2_INTEL_VS_LVSCTL);
+
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nVS Wake registers\n");
+ ret += intel_sprintf(vs_s, false, buf, ret, SDW_SHIM2_INTEL_VS_WAKEEN);
+ ret += intel_sprintf(vs_s, false, buf, ret, SDW_SHIM2_INTEL_VS_WAKESTS);
+
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nVS IOCTL, ACTMCTL\n");
+ ret += intel_sprintf(vs_s, false, buf, ret, SDW_SHIM2_INTEL_VS_IOCTL);
+ ret += intel_sprintf(vs_s, false, buf, ret, SDW_SHIM2_INTEL_VS_ACTMCTL);
+
+ if (sdw->link_res->mic_privacy) {
+ ret += scnprintf(buf + ret, RD_BUF - ret, "\nVS PVCCS\n");
+ ret += intel_sprintf(vs_s, false, buf, ret,
+ SDW_SHIM2_INTEL_VS_PVCCS);
+ }
+
+ 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");
+
+void intel_ace2x_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);
+}
+
+void intel_ace2x_debugfs_exit(struct sdw_intel *sdw)
+{
+ debugfs_remove_recursive(sdw->debugfs);
+}
+#endif /* CONFIG_DEBUG_FS */
diff --git a/drivers/soundwire/intel_auxdevice.c b/drivers/soundwire/intel_auxdevice.c
new file mode 100644
index 000000000000..6df2601fff90
--- /dev/null
+++ b/drivers/soundwire/intel_auxdevice.c
@@ -0,0 +1,871 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-22 Intel Corporation.
+
+/*
+ * Soundwire Intel Manager 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"
+#include "intel_auxdevice.h"
+
+#define INTEL_MASTER_SUSPEND_DELAY_MS 3000
+
+/*
+ * debug/config flags for the Intel SoundWire Master.
+ *
+ * Since we may have multiple masters active, we can have up to 8
+ * flags reused in each byte, with master0 using the ls-byte, etc.
+ */
+
+#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0)
+#define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1)
+#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE BIT(2)
+#define SDW_INTEL_MASTER_DISABLE_MULTI_LINK BIT(3)
+
+static int md_flags;
+module_param_named(sdw_md_flags, md_flags, int, 0444);
+MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)");
+
+static int mclk_divider;
+module_param_named(sdw_mclk_divider, mclk_divider, int, 0444);
+MODULE_PARM_DESC(sdw_mclk_divider, "SoundWire Intel mclk divider");
+
+struct wake_capable_part {
+ const u16 mfg_id;
+ const u16 part_id;
+};
+
+static struct wake_capable_part wake_capable_list[] = {
+ {0x01fa, 0x4243},
+ {0x025d, 0x5682},
+ {0x025d, 0x700},
+ {0x025d, 0x711},
+ {0x025d, 0x1712},
+ {0x025d, 0x1713},
+ {0x025d, 0x1716},
+ {0x025d, 0x1717},
+ {0x025d, 0x712},
+ {0x025d, 0x713},
+ {0x025d, 0x714},
+ {0x025d, 0x715},
+ {0x025d, 0x716},
+ {0x025d, 0x717},
+ {0x025d, 0x721},
+ {0x025d, 0x722},
+};
+
+static bool is_wake_capable(struct sdw_slave *slave)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(wake_capable_list); i++)
+ if (slave->id.part_id == wake_capable_list[i].part_id &&
+ slave->id.mfg_id == wake_capable_list[i].mfg_id)
+ return true;
+ return false;
+}
+
+static int generic_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave,
+ struct sdw_bpt_msg *msg)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+
+ if (sdw->link_res->hw_ops->bpt_send_async)
+ return sdw->link_res->hw_ops->bpt_send_async(sdw, slave, msg);
+ return -EOPNOTSUPP;
+}
+
+static int generic_bpt_wait(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+
+ if (sdw->link_res->hw_ops->bpt_wait)
+ return sdw->link_res->hw_ops->bpt_wait(sdw, slave, msg);
+ return -EOPNOTSUPP;
+}
+
+static int generic_pre_bank_switch(struct sdw_bus *bus)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+
+ return sdw->link_res->hw_ops->pre_bank_switch(sdw);
+}
+
+static int generic_post_bank_switch(struct sdw_bus *bus)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+
+ return sdw->link_res->hw_ops->post_bank_switch(sdw);
+}
+
+static void generic_new_peripheral_assigned(struct sdw_bus *bus,
+ struct sdw_slave *slave,
+ int dev_num)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ int dev_num_min;
+ int dev_num_max;
+ bool wake_capable = slave->prop.wake_capable || is_wake_capable(slave);
+
+ if (wake_capable) {
+ dev_num_min = SDW_INTEL_DEV_NUM_IDA_MIN;
+ dev_num_max = SDW_MAX_DEVICES;
+ } else {
+ dev_num_min = 1;
+ dev_num_max = SDW_INTEL_DEV_NUM_IDA_MIN - 1;
+ }
+
+ /* paranoia check, this should never happen */
+ if (dev_num < dev_num_min || dev_num > dev_num_max) {
+ dev_err(bus->dev, "%s: invalid dev_num %d, wake supported %d\n",
+ __func__, dev_num, slave->prop.wake_capable);
+ return;
+ }
+
+ if (sdw->link_res->hw_ops->program_sdi && wake_capable)
+ sdw->link_res->hw_ops->program_sdi(sdw, dev_num);
+}
+
+static int sdw_master_read_intel_prop(struct sdw_bus *bus)
+{
+ struct sdw_master_prop *prop = &bus->prop;
+ struct sdw_intel_prop *intel_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);
+
+ if (mclk_divider)
+ /* use kernel parameter for BIOS or board work-arounds */
+ prop->mclk_freq /= mclk_divider;
+ else
+ /* 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;
+
+ intel_prop = devm_kzalloc(bus->dev, sizeof(*intel_prop), GFP_KERNEL);
+ if (!intel_prop) {
+ fwnode_handle_put(link);
+ return -ENOMEM;
+ }
+
+ /* initialize with hardware defaults, in case the properties are not found */
+ intel_prop->clde = 0x0;
+ intel_prop->doaise2 = 0x0;
+ intel_prop->dodse2 = 0x0;
+ intel_prop->clds = 0x0;
+ intel_prop->clss = 0x0;
+ intel_prop->doaise = 0x1;
+ intel_prop->doais = 0x3;
+ intel_prop->dodse = 0x0;
+ intel_prop->dods = 0x1;
+
+ fwnode_property_read_u16(link,
+ "intel-sdw-clde",
+ &intel_prop->clde);
+ fwnode_property_read_u16(link,
+ "intel-sdw-doaise2",
+ &intel_prop->doaise2);
+ fwnode_property_read_u16(link,
+ "intel-sdw-dodse2",
+ &intel_prop->dodse2);
+ fwnode_property_read_u16(link,
+ "intel-sdw-clds",
+ &intel_prop->clds);
+ fwnode_property_read_u16(link,
+ "intel-sdw-clss",
+ &intel_prop->clss);
+ fwnode_property_read_u16(link,
+ "intel-sdw-doaise",
+ &intel_prop->doaise);
+ fwnode_property_read_u16(link,
+ "intel-sdw-doais",
+ &intel_prop->doais);
+ fwnode_property_read_u16(link,
+ "intel-sdw-dodse",
+ &intel_prop->dodse);
+ fwnode_property_read_u16(link,
+ "intel-sdw-dods",
+ &intel_prop->dods);
+ bus->vendor_specific_prop = intel_prop;
+
+ dev_dbg(bus->dev, "doaise %#x doais %#x dodse %#x dods %#x\n",
+ intel_prop->doaise,
+ intel_prop->doais,
+ intel_prop->dodse,
+ intel_prop->dods);
+
+ fwnode_handle_put(link);
+
+ 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 DEFINE_IDA(intel_peripheral_ida);
+
+static int intel_get_device_num_ida(struct sdw_bus *bus, struct sdw_slave *slave)
+{
+ int bit;
+
+ if (slave->prop.wake_capable || is_wake_capable(slave))
+ return ida_alloc_range(&intel_peripheral_ida,
+ SDW_INTEL_DEV_NUM_IDA_MIN, SDW_MAX_DEVICES,
+ GFP_KERNEL);
+
+ bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES);
+ if (bit == SDW_MAX_DEVICES)
+ return -ENODEV;
+
+ return bit;
+}
+
+static void intel_put_device_num_ida(struct sdw_bus *bus, struct sdw_slave *slave)
+{
+ if (slave->prop.wake_capable || is_wake_capable(slave))
+ ida_free(&intel_peripheral_ida, slave->dev_num);
+}
+
+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,
+ .set_bus_conf = cdns_bus_conf,
+ .pre_bank_switch = generic_pre_bank_switch,
+ .post_bank_switch = generic_post_bank_switch,
+ .read_ping_status = cdns_read_ping_status,
+ .get_device_num = intel_get_device_num_ida,
+ .put_device_num = intel_put_device_num_ida,
+ .new_peripheral_assigned = generic_new_peripheral_assigned,
+
+ .bpt_send_async = generic_bpt_send_async,
+ .bpt_wait = generic_bpt_wait,
+};
+
+/*
+ * 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->ip_offset = sdw->link_res->ip_offset;
+ cdns->instance = sdw->instance;
+ cdns->msg_count = 0;
+
+ /* single controller for all SoundWire links */
+ bus->controller_id = 0;
+
+ bus->link_id = auxdev->id;
+ bus->clk_stop_timeout = 1;
+
+ /*
+ * paranoia check: make sure ACPI-reported number of links is aligned with
+ * hardware capabilities.
+ */
+ ret = sdw_intel_get_link_count(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: sdw_intel_get_link_count failed: %d\n", __func__, ret);
+ return ret;
+ }
+ if (ret <= sdw->instance) {
+ dev_err(dev, "%s: invalid link id %d, link count %d\n", __func__, auxdev->id, ret);
+ return -EINVAL;
+ }
+
+ 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;
+
+ 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 = sdw_intel_link_power_up(sdw);
+ if (ret)
+ goto err_init;
+
+ /* Register DAIs */
+ ret = sdw_intel_register_dai(sdw);
+ if (ret) {
+ dev_err(dev, "DAI registration failed: %d\n", ret);
+ goto err_power_up;
+ }
+
+ sdw_intel_debugfs_init(sdw);
+
+ /* Enable runtime PM */
+ if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME)) {
+ pm_runtime_set_autosuspend_delay(dev,
+ INTEL_MASTER_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_mark_last_busy(dev);
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ pm_runtime_resume(bus->dev);
+ }
+
+ /* start bus */
+ ret = sdw_intel_start_bus(sdw);
+ if (ret) {
+ dev_err(dev, "bus start failed: %d\n", ret);
+ goto err_pm_runtime;
+ }
+
+ 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_mark_last_busy(bus->dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_idle(dev);
+ }
+
+ sdw->startup_done = true;
+ return 0;
+
+err_pm_runtime:
+ if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME))
+ pm_runtime_disable(dev);
+err_power_up:
+ sdw_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) {
+ sdw_intel_debugfs_exit(sdw);
+ cancel_delayed_work_sync(&cdns->attach_dwork);
+ 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 (!sdw_intel_shim_check_wake(sdw))
+ return 0;
+
+ /* disable WAKEEN interrupt ASAP to prevent interrupt flood */
+ sdw_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
+ */
+
+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_runtime_resume(dev);
+ if (ret < 0) {
+ dev_err(dev, "%s: pm_runtime_resume failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+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_runtime_resume(dev);
+ if (ret < 0) {
+ dev_err(dev, "%s: pm_runtime_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;
+ }
+
+ /* Prevent runtime PM from racing with the code below. */
+ pm_runtime_disable(dev);
+
+ if (pm_runtime_status_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_status_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 {
+ sdw_intel_shim_wake(sdw, false);
+ }
+ }
+
+ return 0;
+ }
+
+ ret = sdw_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 = sdw_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 = sdw_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 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;
+ }
+
+ ret = sdw_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 = sdw_intel_start_bus(sdw);
+ if (ret < 0) {
+ dev_err(dev, "cannot start bus during resume\n");
+ sdw_intel_link_power_down(sdw);
+ return ret;
+ }
+
+ /*
+ * Runtime PM has been disabled in intel_suspend(), so set the status
+ * to active because the device has just been resumed and re-enable
+ * runtime PM.
+ */
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ /*
+ * 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(bus->dev);
+ 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 */
+ sdw_intel_shim_wake(sdw, false);
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+
+ if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
+ ret = sdw_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 = sdw_intel_start_bus(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot start bus after teardown: %d\n", __func__, ret);
+ sdw_intel_link_power_down(sdw);
+ return ret;
+ }
+
+ } else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) {
+ ret = sdw_intel_link_power_up(sdw);
+ if (ret) {
+ dev_err(dev, "%s: power_up failed after bus reset: %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = sdw_intel_start_bus_after_reset(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot start bus after reset: %d\n", __func__, ret);
+ sdw_intel_link_power_down(sdw);
+ return ret;
+ }
+ } else if (!clock_stop_quirks) {
+
+ sdw_intel_check_clock_stop(sdw);
+
+ ret = sdw_intel_link_power_up(sdw);
+ if (ret) {
+ dev_err(dev, "%s: power_up failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = sdw_intel_start_bus_after_clock_stop(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot start bus after clock stop: %d\n", __func__, ret);
+ sdw_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_auxdevice.h b/drivers/soundwire/intel_auxdevice.h
new file mode 100644
index 000000000000..acaae0bd6592
--- /dev/null
+++ b/drivers/soundwire/intel_auxdevice.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* Copyright(c) 2015-2022 Intel Corporation. */
+
+#ifndef __SDW_INTEL_AUXDEVICE_H
+#define __SDW_INTEL_AUXDEVICE_H
+
+int intel_link_startup(struct auxiliary_device *auxdev);
+int intel_link_process_wakeen_event(struct auxiliary_device *auxdev);
+int intel_resume_child_device(struct device *dev, void *data);
+
+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_AUXDEVICE_H */
diff --git a/drivers/soundwire/intel_bus_common.c b/drivers/soundwire/intel_bus_common.c
new file mode 100644
index 000000000000..ad1f8ebdbfc9
--- /dev/null
+++ b/drivers/soundwire/intel_bus_common.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+// Copyright(c) 2015-2023 Intel Corporation
+
+#include <linux/acpi.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"
+
+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_soft_reset(cdns);
+ if (ret < 0) {
+ dev_err(dev, "%s: unable to soft-reset Cadence IP: %d\n", __func__, ret);
+ return ret;
+ }
+
+ /*
+ * follow recommended programming flows to avoid timeouts when
+ * gsync is enabled
+ */
+ if (bus->multi_link)
+ sdw_intel_sync_arm(sdw);
+
+ ret = sdw_cdns_init(cdns);
+ if (ret < 0) {
+ dev_err(dev, "%s: unable to initialize Cadence IP: %d\n", __func__, ret);
+ return ret;
+ }
+
+ sdw_cdns_config_update(cdns);
+
+ if (bus->multi_link) {
+ ret = sdw_intel_sync_go(sdw);
+ if (ret < 0) {
+ dev_err(dev, "%s: sync go failed: %d\n", __func__, ret);
+ return ret;
+ }
+ }
+
+ ret = sdw_cdns_config_update_set_wait(cdns);
+ if (ret < 0) {
+ dev_err(dev, "%s: CONFIG_UPDATE BIT still set\n", __func__);
+ return 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_exit_reset(cdns);
+ if (ret < 0) {
+ dev_err(dev, "%s: unable to exit bus reset sequence: %d\n", __func__, ret);
+ return ret;
+ }
+
+ sdw_cdns_check_self_clearing_bits(cdns, __func__,
+ true, INTEL_MASTER_RESET_ITERATIONS);
+
+ schedule_delayed_work(&cdns->attach_dwork,
+ msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS));
+
+ return 0;
+}
+
+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);
+
+ /*
+ * follow recommended programming flows to avoid
+ * timeouts when gsync is enabled
+ */
+ if (bus->multi_link)
+ sdw_intel_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");
+ if (!clock_stop0)
+ sdw_cdns_enable_interrupt(cdns, false);
+ return ret;
+ }
+
+ if (!clock_stop0) {
+ sdw_cdns_config_update(cdns);
+
+ if (bus->multi_link) {
+ ret = sdw_intel_sync_go(sdw);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "sync go failed during resume\n");
+ return ret;
+ }
+ }
+
+ ret = sdw_cdns_config_update_set_wait(cdns);
+ if (ret < 0) {
+ dev_err(dev, "%s: CONFIG_UPDATE BIT still set\n", __func__);
+ return ret;
+ }
+
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "cannot enable interrupts during resume\n");
+ return ret;
+ }
+
+ ret = sdw_cdns_exit_reset(cdns);
+ if (ret < 0) {
+ dev_err(dev, "unable to exit bus reset sequence during resume\n");
+ return ret;
+ }
+
+ }
+ sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS);
+
+ schedule_delayed_work(&cdns->attach_dwork,
+ msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS));
+
+ return 0;
+}
+
+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__);
+}
+
+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_clock_restart(cdns, false);
+ if (ret < 0) {
+ dev_err(dev, "%s: unable to restart clock: %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = sdw_cdns_enable_interrupt(cdns, true);
+ if (ret < 0) {
+ dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret);
+ return ret;
+ }
+
+ sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS);
+
+ schedule_delayed_work(&cdns->attach_dwork,
+ msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS));
+
+ return 0;
+}
+
+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;
+
+ cancel_delayed_work_sync(&cdns->attach_dwork);
+
+ 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 = sdw_intel_link_power_down(sdw);
+ if (ret) {
+ dev_err(dev, "%s: Link power down failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ sdw_intel_shim_wake(sdw, wake_enable);
+
+ return 0;
+}
+
+/*
+ * bank switch routines
+ */
+
+int intel_pre_bank_switch(struct sdw_intel *sdw)
+{
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_bus *bus = &cdns->bus;
+
+ /* Write to register only for multi-link */
+ if (!bus->multi_link)
+ return 0;
+
+ sdw_intel_sync_arm(sdw);
+
+ return 0;
+}
+
+int intel_post_bank_switch(struct sdw_intel *sdw)
+{
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_bus *bus = &cdns->bus;
+ int ret = 0;
+
+ /* Write to register only for multi-link */
+ if (!bus->multi_link)
+ return 0;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ /*
+ * 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 (sdw_intel_sync_check_cmdsync_unlocked(sdw))
+ ret = sdw_intel_sync_go_unlocked(sdw);
+
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ if (ret < 0)
+ dev_err(sdw->cdns.dev, "Post bank switch failed: %d\n", ret);
+
+ return ret;
+}
diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c
index 5c8a20d99878..4ffdabaf9693 100644
--- a/drivers/soundwire/intel_init.c
+++ b/drivers/soundwire/intel_init.c
@@ -8,194 +8,397 @@
*/
#include <linux/acpi.h>
-#include <linux/platform_device.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 "bus.h"
#include "intel.h"
+#include "intel_auxdevice.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
+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);
-struct sdw_link_data {
- struct sdw_intel_link_res res;
- struct platform_device *pdev;
-};
+ kfree(ldev);
+}
-struct sdw_intel_ctx {
- int count;
- struct sdw_link_data *links;
-};
+/* 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->hw_ops = res->hw_ops;
+ link->mmio_base = res->mmio_base;
+ if (!res->ext) {
+ link->registers = res->mmio_base + SDW_LINK_BASE
+ + (SDW_LINK_SIZE * link_id);
+ link->ip_offset = 0;
+ link->shim = res->mmio_base + res->shim_base;
+ link->alh = res->mmio_base + res->alh_base;
+ link->shim_lock = &ctx->shim_lock;
+ } else {
+ link->registers = res->mmio_base + SDW_IP_BASE(link_id);
+ link->ip_offset = SDW_CADENCE_MCP_IP_OFFSET;
+ link->shim = res->mmio_base + SDW_SHIM2_GENERIC_BASE(link_id);
+ link->shim_vs = res->mmio_base + SDW_SHIM2_VS_BASE(link_id);
+ link->shim_lock = res->eml_lock;
+ link->mic_privacy = res->mic_privacy;
+ }
+
+ link->ops = res->ops;
+ link->dev = res->dev;
+
+ link->clock_stop_quirks = res->clock_stop_quirks;
+ link->shim_mask = &ctx->shim_mask;
+ link->link_mask = ctx->link_mask;
+
+ link->hbus = res->hbus;
+
+ /* 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);
+ }
-static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx)
+ 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)
{
- struct sdw_link_data *link = ctx->links;
+ 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;
- if (!link)
- return 0;
+ link_mask = ctx->link_mask;
for (i = 0; i < ctx->count; i++) {
- if (link->pdev)
- platform_device_unregister(link->pdev);
- link++;
- }
+ if (!(link_mask & BIT(i)))
+ continue;
+
+ ldev = ctx->ldev[i];
- kfree(ctx->links);
- ctx->links = NULL;
+ 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;
}
+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);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_NS(sdw_intel_thread, "SOUNDWIRE_INTEL_INIT");
+
static struct sdw_intel_ctx
-*sdw_intel_add_controller(struct sdw_intel_res *res)
+*sdw_intel_probe_controller(struct sdw_intel_res *res)
{
- struct platform_device_info pdevinfo;
- struct platform_device *pdev;
- struct sdw_link_data *link;
+ struct sdw_intel_link_res *link;
+ struct sdw_intel_link_dev *ldev;
struct sdw_intel_ctx *ctx;
struct acpi_device *adev;
- int ret, i;
- u8 count;
- u32 caps;
+ struct sdw_slave *slave;
+ struct list_head *node;
+ struct sdw_bus *bus;
+ u32 link_mask;
+ int num_slaves = 0;
+ int count;
+ int i;
- if (acpi_bus_get_device(res->handle, &adev))
+ if (!res)
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);
+ adev = acpi_fetch_acpi_dev(res->handle);
+ if (!adev)
+ return NULL;
- /* 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);
+ 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;
- ctx->links = kcalloc(ctx->count, sizeof(*ctx->links), GFP_KERNEL);
- if (!ctx->links)
- goto link_err;
- link = ctx->links;
+ /*
+ * 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;
+ }
- /* Create SDW Master devices */
- for (i = 0; i < count; i++) {
+ 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;
- 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;
-
- link->res.ops = res->ops;
- link->res.arg = res->arg;
-
- 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;
+ 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++;
+ }
- link->pdev = pdev;
- link++;
+ ctx->peripherals = kmalloc(struct_size(ctx->peripherals, array, num_slaves),
+ GFP_KERNEL);
+ if (!ctx->peripherals)
+ goto err;
+ ctx->peripherals->num_peripherals = 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->peripherals->array[i] = slave;
+ i++;
+ }
}
return ctx;
-pdev_err:
- sdw_intel_cleanup_pdev(ctx);
-link_err:
+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 acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level,
- void *cdata, void **return_value)
+static int
+sdw_intel_startup_controller(struct sdw_intel_ctx *ctx)
{
- struct sdw_intel_res *res = cdata;
- struct acpi_device *adev;
+ struct acpi_device *adev = acpi_fetch_acpi_dev(ctx->handle);
+ struct sdw_intel_link_dev *ldev;
+ u32 link_mask;
+ int i;
+
+ if (!adev)
+ return -EINVAL;
+
+ if (!ctx->ldev)
+ return -EINVAL;
+
+ link_mask = ctx->link_mask;
- if (acpi_bus_get_device(handle, &adev)) {
- pr_err("%s: Couldn't find ACPI handle\n", __func__);
- return AE_NOT_FOUND;
+ /* 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);
+ }
}
- res->handle = handle;
- return AE_OK;
+ return 0;
}
/**
- * sdw_intel_init() - SoundWire Intel init routine
- * @parent_handle: ACPI parent handle
+ * sdw_intel_probe() - SoundWire Intel probe routine
* @res: resource data
*
- * This scans the namespace and creates SoundWire link controller devices
- * based on the info queried.
+ * 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.
*/
-void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res)
+struct sdw_intel_ctx
+*sdw_intel_probe(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);
+ return sdw_intel_probe_controller(res);
}
-EXPORT_SYMBOL(sdw_intel_init);
+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
- * @arg: callback context
+ * @ctx: SoundWire context allocated in the probe
*
* Delete the controller instances created and cleanup
*/
-void sdw_intel_exit(void *arg)
+void sdw_intel_exit(struct sdw_intel_ctx *ctx)
{
- struct sdw_intel_ctx *ctx = arg;
+ struct sdw_intel_link_res *link;
- sdw_intel_cleanup_pdev(ctx);
+ /* we first resume links and devices and wait synchronously before the cleanup */
+ list_for_each_entry(link, &ctx->link_list, list) {
+ struct sdw_bus *bus = &link->cdns->bus;
+ int ret;
+
+ ret = device_for_each_child(bus->dev, NULL, intel_resume_child_device);
+ if (ret < 0)
+ dev_err(bus->dev, "%s: intel_resume_child_device failed: %d\n",
+ __func__, ret);
+ }
+
+ sdw_intel_cleanup(ctx);
+ kfree(ctx->peripherals);
+ kfree(ctx->ldev);
kfree(ctx);
}
-EXPORT_SYMBOL(sdw_intel_exit);
+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/irq.c b/drivers/soundwire/irq.c
new file mode 100644
index 000000000000..f18be37efef8
--- /dev/null
+++ b/drivers/soundwire/irq.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <linux/device.h>
+#include <linux/fwnode.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/soundwire/sdw.h>
+#include "irq.h"
+
+static int sdw_irq_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct sdw_bus *bus = h->host_data;
+
+ irq_set_chip_data(virq, bus);
+ irq_set_chip(virq, &bus->irq_chip);
+ irq_set_nested_thread(virq, 1);
+ irq_set_noprobe(virq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops sdw_domain_ops = {
+ .map = sdw_irq_map,
+};
+
+int sdw_irq_create(struct sdw_bus *bus,
+ struct fwnode_handle *fwnode)
+{
+ bus->irq_chip.name = dev_name(bus->dev);
+
+ bus->domain = irq_domain_create_linear(fwnode, SDW_FW_MAX_DEVICES,
+ &sdw_domain_ops, bus);
+ if (!bus->domain) {
+ dev_err(bus->dev, "Failed to add IRQ domain\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void sdw_irq_delete(struct sdw_bus *bus)
+{
+ irq_domain_remove(bus->domain);
+}
+
+static void sdw_irq_dispose_mapping(void *data)
+{
+ struct sdw_slave *slave = data;
+
+ irq_dispose_mapping(slave->irq);
+}
+
+void sdw_irq_create_mapping(struct sdw_slave *slave)
+{
+ slave->irq = irq_create_mapping(slave->bus->domain, slave->index);
+ if (!slave->irq)
+ dev_warn(&slave->dev, "Failed to map IRQ\n");
+
+ devm_add_action_or_reset(&slave->dev, sdw_irq_dispose_mapping, slave);
+}
diff --git a/drivers/soundwire/irq.h b/drivers/soundwire/irq.h
new file mode 100644
index 000000000000..86e2318409da
--- /dev/null
+++ b/drivers/soundwire/irq.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef __SDW_IRQ_H
+#define __SDW_IRQ_H
+
+#include <linux/soundwire/sdw.h>
+#include <linux/fwnode.h>
+
+#if IS_ENABLED(CONFIG_IRQ_DOMAIN)
+
+int sdw_irq_create(struct sdw_bus *bus,
+ struct fwnode_handle *fwnode);
+void sdw_irq_delete(struct sdw_bus *bus);
+void sdw_irq_create_mapping(struct sdw_slave *slave);
+
+#else /* CONFIG_IRQ_DOMAIN */
+
+static inline int sdw_irq_create(struct sdw_bus *bus,
+ struct fwnode_handle *fwnode)
+{
+ return 0;
+}
+
+static inline void sdw_irq_delete(struct sdw_bus *bus)
+{
+}
+
+static inline void sdw_irq_create_mapping(struct sdw_slave *slave)
+{
+}
+
+#endif /* CONFIG_IRQ_DOMAIN */
+
+#endif /* __SDW_IRQ_H */
diff --git a/drivers/soundwire/master.c b/drivers/soundwire/master.c
new file mode 100644
index 000000000000..b2c64512739d
--- /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)
+};
+
+const 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-%d", bus->controller_id, bus->link_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
index fdeba0c3b589..c69b78cd0b62 100644
--- a/drivers/soundwire/mipi_disco.c
+++ b/drivers/soundwire/mipi_disco.c
@@ -23,6 +23,26 @@
#include <linux/soundwire/sdw.h>
#include "bus.h"
+static bool mipi_fwnode_property_read_bool(const struct fwnode_handle *fwnode,
+ const char *propname)
+{
+ int ret;
+ u8 val;
+
+ if (!fwnode_property_present(fwnode, propname))
+ return false;
+ ret = fwnode_property_read_u8_array(fwnode, propname, &val, 1);
+ if (ret < 0)
+ return false;
+ return !!val;
+}
+
+static bool mipi_device_property_read_bool(const struct device *dev,
+ const char *propname)
+{
+ return mipi_fwnode_property_read_bool(dev_fwnode(dev), propname);
+}
+
/**
* sdw_master_read_prop() - Read Master properties
* @bus: SDW bus instance
@@ -31,15 +51,19 @@ int sdw_master_read_prop(struct sdw_bus *bus)
{
struct sdw_master_prop *prop = &bus->prop;
struct fwnode_handle *link;
+ const char *scales_prop;
char name[32];
- int nval, i;
+ int nval;
+ int ret;
+ int i;
device_property_read_u32(bus->dev,
- "mipi-sdw-sw-interface-revision", &prop->revision);
+ "mipi-sdw-sw-interface-revision",
+ &prop->revision);
/* Find master handle */
snprintf(name, sizeof(name),
- "mipi-sdw-master-%d-subproperties", bus->link_id);
+ "mipi-sdw-link-%d-subproperties", bus->link_id);
link = device_get_named_child_node(bus->dev, name);
if (!link) {
@@ -47,125 +71,161 @@ int sdw_master_read_prop(struct sdw_bus *bus)
return -EIO;
}
- if (fwnode_property_read_bool(link,
- "mipi-sdw-clock-stop-mode0-supported") == true)
- prop->clk_stop_mode = SDW_CLK_STOP_MODE0;
+ if (mipi_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") == true)
- prop->clk_stop_mode |= SDW_CLK_STOP_MODE1;
+ if (mipi_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_freq);
+ "mipi-sdw-max-clock-frequency",
+ &prop->max_clk_freq);
- nval = fwnode_property_read_u32_array(link,
- "mipi-sdw-clock-frequencies-supported", NULL, 0);
+ nval = fwnode_property_count_u32(link, "mipi-sdw-clock-frequencies-supported");
if (nval > 0) {
-
- prop->num_freq = nval;
- prop->freq = devm_kcalloc(bus->dev, prop->num_freq,
- sizeof(*prop->freq), GFP_KERNEL);
- if (!prop->freq)
+ 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) {
+ fwnode_handle_put(link);
return -ENOMEM;
+ }
- fwnode_property_read_u32_array(link,
+ ret = fwnode_property_read_u32_array(link,
"mipi-sdw-clock-frequencies-supported",
- prop->freq, prop->num_freq);
+ prop->clk_freq, prop->num_clk_freq);
+ if (ret < 0)
+ return ret;
}
/*
* 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];
+ 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_read_u32_array(link,
- "mipi-sdw-supported-clock-gears", NULL, 0);
+ scales_prop = "mipi-sdw-supported-clock-scales";
+ nval = fwnode_property_count_u32(link, scales_prop);
+ if (nval == 0) {
+ scales_prop = "mipi-sdw-supported-clock-gears";
+ nval = fwnode_property_count_u32(link, scales_prop);
+ }
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)
+ sizeof(*prop->clk_gears),
+ GFP_KERNEL);
+ if (!prop->clk_gears) {
+ fwnode_handle_put(link);
return -ENOMEM;
+ }
- fwnode_property_read_u32_array(link,
- "mipi-sdw-supported-clock-gears",
- prop->clk_gears, prop->num_clk_gears);
+ ret = fwnode_property_read_u32_array(link,
+ scales_prop,
+ prop->clk_gears,
+ prop->num_clk_gears);
+ if (ret < 0)
+ return ret;
}
fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate",
- &prop->default_frame_rate);
+ &prop->default_frame_rate);
fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size",
- &prop->default_row);
+ &prop->default_row);
fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size",
- &prop->default_col);
+ &prop->default_col);
- prop->dynamic_frame = fwnode_property_read_bool(link,
+ prop->dynamic_frame = mipi_fwnode_property_read_bool(link,
"mipi-sdw-dynamic-frame-shape");
fwnode_property_read_u32(link, "mipi-sdw-command-error-threshold",
- &prop->err_threshold);
+ &prop->err_threshold);
+
+ fwnode_handle_put(link);
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)
+ struct fwnode_handle *port,
+ struct sdw_dp0_prop *dp0)
{
int nval;
+ int ret;
fwnode_property_read_u32(port, "mipi-sdw-port-max-wordlength",
- &dp0->max_word);
+ &dp0->max_word);
fwnode_property_read_u32(port, "mipi-sdw-port-min-wordlength",
- &dp0->min_word);
+ &dp0->min_word);
- nval = fwnode_property_read_u32_array(port,
- "mipi-sdw-port-wordlength-configs", NULL, 0);
+ 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);
+ dp0->num_words, sizeof(*dp0->words),
+ GFP_KERNEL);
if (!dp0->words)
return -ENOMEM;
- fwnode_property_read_u32_array(port,
+ ret = fwnode_property_read_u32_array(port,
"mipi-sdw-port-wordlength-configs",
dp0->words, dp0->num_words);
+ if (ret < 0)
+ return ret;
}
- dp0->flow_controlled = fwnode_property_read_bool(
- port, "mipi-sdw-bra-flow-controlled");
+ dp0->BRA_flow_controlled = mipi_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->simple_ch_prep_sm = mipi_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");
+ dp0->imp_def_interrupts = mipi_fwnode_property_read_bool(port,
+ "mipi-sdw-imp-def-dp0-interrupts-supported");
+
+ nval = fwnode_property_count_u32(port, "mipi-sdw-lane-list");
+ if (nval > 0) {
+ dp0->num_lanes = nval;
+ dp0->lane_list = devm_kcalloc(&slave->dev,
+ dp0->num_lanes, sizeof(*dp0->lane_list),
+ GFP_KERNEL);
+ if (!dp0->lane_list)
+ return -ENOMEM;
+
+ ret = fwnode_property_read_u32_array(port,
+ "mipi-sdw-lane-list",
+ dp0->lane_list, dp0->num_lanes);
+ if (ret < 0)
+ return ret;
+ }
return 0;
}
static int sdw_slave_read_dpn(struct sdw_slave *slave,
- struct sdw_dpn_prop *dpn, int count, int ports, char *type)
+ 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];
+ int nval;
+ int ret;
addr = ports;
/* valid ports are 1 to 14 so apply mask */
@@ -173,7 +233,7 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave,
for_each_set_bit(bit, &addr, 32) {
snprintf(name, sizeof(name),
- "mipi-sdw-dp-%d-%s-subproperties", bit, type);
+ "mipi-sdw-dp-%d-%s-subproperties", bit, type);
dpn[i].num = bit;
@@ -184,96 +244,121 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave,
}
fwnode_property_read_u32(node, "mipi-sdw-port-max-wordlength",
- &dpn[i].max_word);
+ &dpn[i].max_word);
fwnode_property_read_u32(node, "mipi-sdw-port-min-wordlength",
- &dpn[i].min_word);
+ &dpn[i].min_word);
- nval = fwnode_property_read_u32_array(node,
- "mipi-sdw-port-wordlength-configs", NULL, 0);
+ 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)
+ dpn[i].num_words,
+ sizeof(*dpn[i].words),
+ GFP_KERNEL);
+ if (!dpn[i].words) {
+ fwnode_handle_put(node);
return -ENOMEM;
+ }
- fwnode_property_read_u32_array(node,
+ ret = fwnode_property_read_u32_array(node,
"mipi-sdw-port-wordlength-configs",
dpn[i].words, dpn[i].num_words);
+ if (ret < 0)
+ return ret;
}
fwnode_property_read_u32(node, "mipi-sdw-data-port-type",
- &dpn[i].type);
+ &dpn[i].type);
fwnode_property_read_u32(node,
- "mipi-sdw-max-grouping-supported",
- &dpn[i].max_grouping);
+ "mipi-sdw-max-grouping-supported",
+ &dpn[i].max_grouping);
- dpn[i].simple_ch_prep_sm = fwnode_property_read_bool(node,
+ dpn[i].simple_ch_prep_sm = mipi_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);
+ "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);
+ &dpn[i].imp_def_interrupts);
fwnode_property_read_u32(node, "mipi-sdw-min-channel-number",
- &dpn[i].min_ch);
+ &dpn[i].min_ch);
fwnode_property_read_u32(node, "mipi-sdw-max-channel-number",
- &dpn[i].max_ch);
+ &dpn[i].max_ch);
- nval = fwnode_property_read_u32_array(node,
- "mipi-sdw-channel-number-list", NULL, 0);
+ nval = fwnode_property_count_u32(node, "mipi-sdw-channel-number-list");
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)
+ 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) {
+ fwnode_handle_put(node);
return -ENOMEM;
+ }
- fwnode_property_read_u32_array(node,
+ ret = fwnode_property_read_u32_array(node,
"mipi-sdw-channel-number-list",
- dpn[i].ch, dpn[i].num_ch);
+ dpn[i].channels, dpn[i].num_channels);
+ if (ret < 0)
+ return ret;
}
- nval = fwnode_property_read_u32_array(node,
- "mipi-sdw-channel-combination-list", NULL, 0);
+ 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)
+ if (!dpn[i].ch_combinations) {
+ fwnode_handle_put(node);
return -ENOMEM;
+ }
- fwnode_property_read_u32_array(node,
+ ret = fwnode_property_read_u32_array(node,
"mipi-sdw-channel-combination-list",
dpn[i].ch_combinations,
dpn[i].num_ch_combinations);
+ if (ret < 0)
+ return ret;
}
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].max_async_buffer);
- dpn[i].block_pack_mode = fwnode_property_read_bool(node,
+ dpn[i].block_pack_mode = mipi_fwnode_property_read_bool(node,
"mipi-sdw-block-packing-mode");
fwnode_property_read_u32(node, "mipi-sdw-port-encoding-type",
- &dpn[i].port_encoding);
+ &dpn[i].port_encoding);
+
+ nval = fwnode_property_count_u32(node, "mipi-sdw-lane-list");
+ if (nval > 0) {
+ dpn[i].num_lanes = nval;
+ dpn[i].lane_list = devm_kcalloc(&slave->dev,
+ dpn[i].num_lanes, sizeof(*dpn[i].lane_list),
+ GFP_KERNEL);
+ if (!dpn[i].lane_list)
+ return -ENOMEM;
+
+ ret = fwnode_property_read_u32_array(node,
+ "mipi-sdw-lane-list",
+ dpn[i].lane_list, dpn[i].num_lanes);
+ if (ret < 0)
+ return ret;
+ }
- /* TODO: Read audio mode */
+ fwnode_handle_put(node);
i++;
}
@@ -281,6 +366,44 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave,
return 0;
}
+/*
+ * In MIPI DisCo spec for SoundWire, lane mapping for a slave device is done with
+ * mipi-sdw-lane-x-mapping properties, where x is 1..7, and the values for those
+ * properties are mipi-sdw-manager-lane-x or mipi-sdw-peripheral-link-y, where x
+ * is an integer between 1 to 7 if the lane is connected to a manager lane, y is a
+ * character between A to E if the lane is connected to another peripheral lane.
+ */
+int sdw_slave_read_lane_mapping(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ struct device *dev = &slave->dev;
+ char prop_name[30];
+ const char *prop_val;
+ size_t len;
+ int ret, i;
+ u8 lane;
+
+ for (i = 0; i < SDW_MAX_LANES; i++) {
+ snprintf(prop_name, sizeof(prop_name), "mipi-sdw-lane-%d-mapping", i);
+ ret = device_property_read_string(dev, prop_name, &prop_val);
+ if (ret)
+ continue;
+
+ len = strlen(prop_val);
+ if (len < 1)
+ return -EINVAL;
+
+ /* The last character is enough to identify the connection */
+ ret = kstrtou8(&prop_val[len - 1], 10, &lane);
+ if (ret)
+ return ret;
+ if (in_range(lane, 1, SDW_MAX_LANES - 1))
+ prop->lane_maps[i] = lane;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(sdw_slave_read_lane_mapping);
+
/**
* sdw_slave_read_prop() - Read Slave properties
* @slave: SDW Slave
@@ -290,70 +413,87 @@ int sdw_slave_read_prop(struct sdw_slave *slave)
struct sdw_slave_prop *prop = &slave->prop;
struct device *dev = &slave->dev;
struct fwnode_handle *port;
- int num_of_ports, nval, i, dp0 = 0;
+ int nval;
+ int ret;
device_property_read_u32(dev, "mipi-sdw-sw-interface-revision",
- &prop->mipi_revision);
+ &prop->mipi_revision);
- prop->wake_capable = device_property_read_bool(dev,
+ prop->wake_capable = mipi_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,
+ prop->test_mode_capable = mipi_device_property_read_bool(dev,
"mipi-sdw-test-mode-supported");
prop->clk_stop_mode1 = false;
- if (device_property_read_bool(dev,
+ if (mipi_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,
+ prop->simple_clk_stop_capable = mipi_device_property_read_bool(dev,
"mipi-sdw-simplified-clockstopprepare-sm-supported");
device_property_read_u32(dev, "mipi-sdw-clockstopprepare-timeout",
- &prop->clk_stop_timeout);
+ &prop->clk_stop_timeout);
- device_property_read_u32(dev, "mipi-sdw-slave-channelprepare-timeout",
- &prop->ch_prep_timeout);
+ ret = device_property_read_u32(dev, "mipi-sdw-peripheral-channelprepare-timeout",
+ &prop->ch_prep_timeout);
+ if (ret < 0)
+ 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,
+ prop->high_PHY_capable = mipi_device_property_read_bool(dev,
"mipi-sdw-highPHY-capable");
- prop->paging_support = device_property_read_bool(dev,
- "mipi-sdw-paging-support");
+ prop->paging_support = mipi_device_property_read_bool(dev,
+ "mipi-sdw-paging-supported");
- prop->bank_delay_support = device_property_read_bool(dev,
- "mipi-sdw-bank-delay-support");
+ prop->bank_delay_support = mipi_device_property_read_bool(dev,
+ "mipi-sdw-bank-delay-supported");
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);
+ &prop->master_count);
device_property_read_u32(dev, "mipi-sdw-source-port-list",
- &prop->source_ports);
+ &prop->source_ports);
device_property_read_u32(dev, "mipi-sdw-sink-port-list",
- &prop->sink_ports);
+ &prop->sink_ports);
- /* Read dp0 properties */
+ device_property_read_u32(dev, "mipi-sdw-sdca-interrupt-register-list",
+ &prop->sdca_interrupt_register_list);
+
+ prop->commit_register_supported = mipi_device_property_read_bool(dev,
+ "mipi-sdw-commit-register-supported");
+
+ /*
+ * Read dp0 properties - we don't rely on the 'mipi-sdw-dp-0-supported'
+ * property since the 'mipi-sdw-dp0-subproperties' property is logically
+ * equivalent.
+ */
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)
+ sizeof(*prop->dp0_prop),
+ GFP_KERNEL);
+ if (!prop->dp0_prop) {
+ fwnode_handle_put(port);
return -ENOMEM;
+ }
sdw_slave_read_dp0(slave, port, prop->dp0_prop);
- dp0 = 1;
+
+ fwnode_handle_put(port);
}
/*
@@ -364,38 +504,26 @@ int sdw_slave_read_prop(struct sdw_slave *slave)
/* 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);
+ 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");
+ 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);
+ 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]);
+ prop->sink_ports, "sink");
- return 0;
+ return sdw_slave_read_lane_mapping(slave);
}
EXPORT_SYMBOL(sdw_slave_read_prop);
diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c
new file mode 100644
index 000000000000..5b3078220189
--- /dev/null
+++ b/drivers/soundwire/qcom.c
@@ -0,0 +1,1789 @@
+// 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/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_LINK_MANAGER_EE 0x018
+#define SWRM_EE_CPU 1
+#define SWRM_FRM_GEN_ENABLED BIT(0)
+#define SWRM_VERSION_1_3_0 0x01030000
+#define SWRM_VERSION_1_5_1 0x01050001
+#define SWRM_VERSION_1_7_0 0x01070000
+#define SWRM_VERSION_2_0_0 0x02000000
+#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_V1_3_INTERRUPT_STATUS 0x200
+#define SWRM_V2_0_INTERRUPT_STATUS 0x5000
+#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_AUTO_ENUM_FAILED BIT(11)
+#define SWRM_INTERRUPT_STATUS_AUTO_ENUM_TABLE_IS_FULL BIT(12)
+#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_STATUS_CMD_IGNORED_AND_EXEC_CONTINUED BIT(19)
+#define SWRM_INTERRUPT_MAX 17
+#define SWRM_V1_3_INTERRUPT_MASK_ADDR 0x204
+#define SWRM_V1_3_INTERRUPT_CLEAR 0x208
+#define SWRM_V2_0_INTERRUPT_CLEAR 0x5008
+#define SWRM_V1_3_INTERRUPT_CPU_EN 0x210
+#define SWRM_V2_0_INTERRUPT_CPU_EN 0x5004
+#define SWRM_V1_3_CMD_FIFO_WR_CMD 0x300
+#define SWRM_V2_0_CMD_FIFO_WR_CMD 0x5020
+#define SWRM_V1_3_CMD_FIFO_RD_CMD 0x304
+#define SWRM_V2_0_CMD_FIFO_RD_CMD 0x5024
+#define SWRM_CMD_FIFO_CMD 0x308
+#define SWRM_CMD_FIFO_FLUSH 0x1
+#define SWRM_V1_3_CMD_FIFO_STATUS 0x30C
+#define SWRM_V2_0_CMD_FIFO_STATUS 0x5050
+#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_V1_3_CMD_FIFO_RD_FIFO_ADDR 0x318
+#define SWRM_V2_0_CMD_FIFO_RD_FIFO_ADDR 0x5040
+#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_DP_SAMPLECTRL2_BANK(n, m) (0x113C + 0x100 * (n - 1) + 0x40 * m)
+#define SWRM_DIN_DPn_PCM_PORT_CTRL(n) (0x1054 + 0x100 * (n - 1))
+#define SWR_V1_3_MSTR_MAX_REG_ADDR 0x1740
+#define SWR_V2_0_MSTR_MAX_REG_ADDR 0x50ac
+
+#define SWRM_V2_0_CLK_CTRL 0x5060
+#define SWRM_V2_0_CLK_CTRL_CLK_START BIT(0)
+#define SWRM_V2_0_LINK_STATUS 0x5064
+
+#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 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 {
+ u16 si;
+ u8 off1;
+ u8 off2;
+ u8 bp_mode;
+ u8 hstart;
+ u8 hstop;
+ u8 word_length;
+ u8 blk_group_count;
+ u8 lane_control;
+};
+
+/*
+ * Internal IDs for different register layouts. Only few registers differ per
+ * each variant, so the list of IDs below does not include all of registers.
+ */
+enum {
+ SWRM_REG_FRAME_GEN_ENABLED,
+ SWRM_REG_INTERRUPT_STATUS,
+ SWRM_REG_INTERRUPT_MASK_ADDR,
+ SWRM_REG_INTERRUPT_CLEAR,
+ SWRM_REG_INTERRUPT_CPU_EN,
+ SWRM_REG_CMD_FIFO_WR_CMD,
+ SWRM_REG_CMD_FIFO_RD_CMD,
+ SWRM_REG_CMD_FIFO_STATUS,
+ SWRM_REG_CMD_FIFO_RD_FIFO_ADDR,
+};
+
+struct qcom_swrm_ctrl {
+ struct sdw_bus bus;
+ struct device *dev;
+ struct regmap *regmap;
+ u32 max_reg;
+ const unsigned int *reg_layout;
+ void __iomem *mmio;
+ struct reset_control *audio_cgcr;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs;
+#endif
+ struct completion broadcast;
+ struct completion enumeration;
+ /* Port alloc/free lock */
+ struct mutex port_lock;
+ struct clk *hclk;
+ int irq;
+ unsigned int version;
+ int wake_irq;
+ int num_din_ports;
+ int num_dout_ports;
+ int cols_index;
+ int rows_index;
+ unsigned long 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;
+ u32 max_reg;
+ const unsigned int *reg_layout;
+};
+
+static const unsigned int swrm_v1_3_reg_layout[] = {
+ [SWRM_REG_FRAME_GEN_ENABLED] = SWRM_COMP_STATUS,
+ [SWRM_REG_INTERRUPT_STATUS] = SWRM_V1_3_INTERRUPT_STATUS,
+ [SWRM_REG_INTERRUPT_MASK_ADDR] = SWRM_V1_3_INTERRUPT_MASK_ADDR,
+ [SWRM_REG_INTERRUPT_CLEAR] = SWRM_V1_3_INTERRUPT_CLEAR,
+ [SWRM_REG_INTERRUPT_CPU_EN] = SWRM_V1_3_INTERRUPT_CPU_EN,
+ [SWRM_REG_CMD_FIFO_WR_CMD] = SWRM_V1_3_CMD_FIFO_WR_CMD,
+ [SWRM_REG_CMD_FIFO_RD_CMD] = SWRM_V1_3_CMD_FIFO_RD_CMD,
+ [SWRM_REG_CMD_FIFO_STATUS] = SWRM_V1_3_CMD_FIFO_STATUS,
+ [SWRM_REG_CMD_FIFO_RD_FIFO_ADDR] = SWRM_V1_3_CMD_FIFO_RD_FIFO_ADDR,
+};
+
+static const struct qcom_swrm_data swrm_v1_3_data = {
+ .default_rows = 48,
+ .default_cols = 16,
+ .max_reg = SWR_V1_3_MSTR_MAX_REG_ADDR,
+ .reg_layout = swrm_v1_3_reg_layout,
+};
+
+static const struct qcom_swrm_data swrm_v1_5_data = {
+ .default_rows = 50,
+ .default_cols = 16,
+ .max_reg = SWR_V1_3_MSTR_MAX_REG_ADDR,
+ .reg_layout = swrm_v1_3_reg_layout,
+};
+
+static const struct qcom_swrm_data swrm_v1_6_data = {
+ .default_rows = 50,
+ .default_cols = 16,
+ .sw_clk_gate_required = true,
+ .max_reg = SWR_V1_3_MSTR_MAX_REG_ADDR,
+ .reg_layout = swrm_v1_3_reg_layout,
+};
+
+static const unsigned int swrm_v2_0_reg_layout[] = {
+ [SWRM_REG_FRAME_GEN_ENABLED] = SWRM_V2_0_LINK_STATUS,
+ [SWRM_REG_INTERRUPT_STATUS] = SWRM_V2_0_INTERRUPT_STATUS,
+ [SWRM_REG_INTERRUPT_MASK_ADDR] = 0, /* Not present */
+ [SWRM_REG_INTERRUPT_CLEAR] = SWRM_V2_0_INTERRUPT_CLEAR,
+ [SWRM_REG_INTERRUPT_CPU_EN] = SWRM_V2_0_INTERRUPT_CPU_EN,
+ [SWRM_REG_CMD_FIFO_WR_CMD] = SWRM_V2_0_CMD_FIFO_WR_CMD,
+ [SWRM_REG_CMD_FIFO_RD_CMD] = SWRM_V2_0_CMD_FIFO_RD_CMD,
+ [SWRM_REG_CMD_FIFO_STATUS] = SWRM_V2_0_CMD_FIFO_STATUS,
+ [SWRM_REG_CMD_FIFO_RD_FIFO_ADDR] = SWRM_V2_0_CMD_FIFO_RD_FIFO_ADDR,
+};
+
+static const struct qcom_swrm_data swrm_v2_0_data = {
+ .default_rows = 50,
+ .default_cols = 16,
+ .sw_clk_gate_required = true,
+ .max_reg = SWR_V2_0_MSTR_MAX_REG_ADDR,
+ .reg_layout = swrm_v2_0_reg_layout,
+};
+
+#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 *ctrl)
+{
+ u32 fifo_outstanding_data, value;
+ int fifo_retry_count = SWR_OVERFLOW_RETRY_COUNT;
+
+ do {
+ /* Check for fifo underflow during read */
+ ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_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(ctrl->dev, "%s err read underflow\n", __func__);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int swrm_wait_for_wr_fifo_avail(struct qcom_swrm_ctrl *ctrl)
+{
+ u32 fifo_outstanding_cmds, value;
+ int fifo_retry_count = SWR_OVERFLOW_RETRY_COUNT;
+
+ do {
+ /* Check for fifo overflow during write */
+ ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_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 < ctrl->wr_fifo_depth)
+ return 0;
+
+ usleep_range(500, 510);
+ } while (fifo_retry_count--);
+
+ if (fifo_outstanding_cmds == ctrl->wr_fifo_depth) {
+ dev_err_ratelimited(ctrl->dev, "%s err write overflow\n", __func__);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static bool swrm_wait_for_wr_fifo_done(struct qcom_swrm_ctrl *ctrl)
+{
+ u32 fifo_outstanding_cmds, value;
+ int fifo_retry_count = SWR_OVERFLOW_RETRY_COUNT;
+
+ /* Check for fifo overflow during write */
+ ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_CMD_FIFO_STATUS], &value);
+ fifo_outstanding_cmds = FIELD_GET(SWRM_WR_CMD_FIFO_CNT_MASK, value);
+
+ if (fifo_outstanding_cmds) {
+ while (fifo_retry_count) {
+ usleep_range(500, 510);
+ ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_CMD_FIFO_STATUS], &value);
+ fifo_outstanding_cmds = FIELD_GET(SWRM_WR_CMD_FIFO_CNT_MASK, value);
+ fifo_retry_count--;
+ if (fifo_outstanding_cmds == 0)
+ return true;
+ }
+ } else {
+ return true;
+ }
+
+
+ return false;
+}
+
+static int qcom_swrm_cmd_fifo_wr_cmd(struct qcom_swrm_ctrl *ctrl, 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(&ctrl->wcmd_id, cmd_data,
+ dev_addr, reg_addr);
+ }
+
+ if (swrm_wait_for_wr_fifo_avail(ctrl))
+ return SDW_CMD_FAIL_OTHER;
+
+ if (cmd_id == SWR_BROADCAST_CMD_ID)
+ reinit_completion(&ctrl->broadcast);
+
+ /* Its assumed that write is okay as we do not get any status back */
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_CMD_FIFO_WR_CMD], val);
+
+ if (ctrl->version <= SWRM_VERSION_1_3_0)
+ usleep_range(150, 155);
+
+ if (cmd_id == SWR_BROADCAST_CMD_ID) {
+ swrm_wait_for_wr_fifo_done(ctrl);
+ /*
+ * sleep for 10ms for MSM soundwire variant to allow broadcast
+ * command to complete.
+ */
+ ret = wait_for_completion_timeout(&ctrl->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 *ctrl,
+ 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(&ctrl->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(ctrl);
+
+ /* wait for FIFO RD to complete to avoid overflow */
+ usleep_range(100, 105);
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_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(ctrl))
+ return SDW_CMD_FAIL_OTHER;
+
+ do {
+ ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_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 != ctrl->rcmd_id) {
+ if (retry_attempt < (MAX_FIFO_RD_RETRY - 1)) {
+ /* wait 500 us before retry on fifo read failure */
+ usleep_range(500, 505);
+ ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CMD,
+ SWRM_CMD_FIFO_FLUSH);
+ ctrl->reg_write(ctrl,
+ ctrl->reg_layout[SWRM_REG_CMD_FIFO_RD_CMD],
+ val);
+ }
+ retry_attempt++;
+ } else {
+ return SDW_CMD_OK;
+ }
+
+ } while (retry_attempt < MAX_FIFO_RD_RETRY);
+
+ dev_err(ctrl->dev, "failed to read fifo: reg: 0x%x, rcmd_id: 0x%x,\
+ dev_num: 0x%x, cmd_data: 0x%x\n",
+ reg_addr, ctrl->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;
+ ctrl->clock_stop_not_supported = 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);
+ if (slave->prop.clk_stop_mode1)
+ ctrl->clock_stop_not_supported = true;
+
+ 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 *ctrl = dev_id;
+ int ret;
+
+ ret = pm_runtime_get_sync(ctrl->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(ctrl->dev,
+ "pm_runtime_get_sync failed in %s, ret %d\n",
+ __func__, ret);
+ pm_runtime_put_noidle(ctrl->dev);
+ return ret;
+ }
+
+ if (ctrl->wake_irq > 0) {
+ if (!irqd_irq_disabled(irq_get_irq_data(ctrl->wake_irq)))
+ disable_irq_nosync(ctrl->wake_irq);
+ }
+
+ pm_runtime_mark_last_busy(ctrl->dev);
+ pm_runtime_put_autosuspend(ctrl->dev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)
+{
+ struct qcom_swrm_ctrl *ctrl = dev_id;
+ u32 value, intr_sts, intr_sts_masked, slave_status;
+ u32 i;
+ int devnum;
+ int ret = IRQ_HANDLED;
+ clk_prepare_enable(ctrl->hclk);
+
+ ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_STATUS],
+ &intr_sts);
+ intr_sts_masked = intr_sts & ctrl->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(ctrl);
+ if (devnum < 0) {
+ dev_err_ratelimited(ctrl->dev,
+ "no slave alert found.spurious interrupt\n");
+ } else {
+ sdw_handle_slave_status(&ctrl->bus, ctrl->status);
+ }
+
+ break;
+ case SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED:
+ case SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS:
+ dev_dbg_ratelimited(ctrl->dev, "SWR new slave attached\n");
+ ctrl->reg_read(ctrl, SWRM_MCP_SLV_STATUS, &slave_status);
+ if (ctrl->slave_status == slave_status) {
+ dev_dbg(ctrl->dev, "Slave status not changed %x\n",
+ slave_status);
+ } else {
+ qcom_swrm_get_device_status(ctrl);
+ qcom_swrm_enumerate(&ctrl->bus);
+ sdw_handle_slave_status(&ctrl->bus, ctrl->status);
+ }
+ break;
+ case SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET:
+ dev_err_ratelimited(ctrl->dev,
+ "%s: SWR bus clsh detected\n",
+ __func__);
+ ctrl->intr_mask &= ~SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
+ ctrl->reg_write(ctrl,
+ ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
+ ctrl->intr_mask);
+ break;
+ case SWRM_INTERRUPT_STATUS_RD_FIFO_OVERFLOW:
+ ctrl->reg_read(ctrl,
+ ctrl->reg_layout[SWRM_REG_CMD_FIFO_STATUS],
+ &value);
+ dev_err_ratelimited(ctrl->dev,
+ "%s: SWR read FIFO overflow fifo status 0x%x\n",
+ __func__, value);
+ break;
+ case SWRM_INTERRUPT_STATUS_RD_FIFO_UNDERFLOW:
+ ctrl->reg_read(ctrl,
+ ctrl->reg_layout[SWRM_REG_CMD_FIFO_STATUS],
+ &value);
+ dev_err_ratelimited(ctrl->dev,
+ "%s: SWR read FIFO underflow fifo status 0x%x\n",
+ __func__, value);
+ break;
+ case SWRM_INTERRUPT_STATUS_WR_CMD_FIFO_OVERFLOW:
+ ctrl->reg_read(ctrl,
+ ctrl->reg_layout[SWRM_REG_CMD_FIFO_STATUS],
+ &value);
+ dev_err(ctrl->dev,
+ "%s: SWR write FIFO overflow fifo status %x\n",
+ __func__, value);
+ ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CMD, 0x1);
+ break;
+ case SWRM_INTERRUPT_STATUS_CMD_ERROR:
+ ctrl->reg_read(ctrl,
+ ctrl->reg_layout[SWRM_REG_CMD_FIFO_STATUS],
+ &value);
+ dev_err_ratelimited(ctrl->dev,
+ "%s: SWR CMD error, fifo status 0x%x, flushing fifo\n",
+ __func__, value);
+ ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CMD, 0x1);
+ break;
+ case SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION:
+ dev_err_ratelimited(ctrl->dev,
+ "%s: SWR Port collision detected\n",
+ __func__);
+ ctrl->intr_mask &= ~SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION;
+ ctrl->reg_write(ctrl,
+ ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
+ ctrl->intr_mask);
+ break;
+ case SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH:
+ dev_err_ratelimited(ctrl->dev,
+ "%s: SWR read enable valid mismatch\n",
+ __func__);
+ ctrl->intr_mask &=
+ ~SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH;
+ ctrl->reg_write(ctrl,
+ ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
+ ctrl->intr_mask);
+ break;
+ case SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED:
+ complete(&ctrl->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;
+ case SWRM_INTERRUPT_STATUS_CMD_IGNORED_AND_EXEC_CONTINUED:
+ ctrl->reg_read(ctrl,
+ ctrl->reg_layout[SWRM_REG_CMD_FIFO_STATUS],
+ &value);
+ dev_err(ctrl->dev,
+ "%s: SWR CMD ignored, fifo status %x\n",
+ __func__, value);
+
+ /* Wait 3.5ms to clear */
+ usleep_range(3500, 3505);
+ break;
+ default:
+ dev_err_ratelimited(ctrl->dev,
+ "%s: SWR unknown interrupt value: %d\n",
+ __func__, value);
+ ret = IRQ_NONE;
+ break;
+ }
+ }
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CLEAR],
+ intr_sts);
+ ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_STATUS],
+ &intr_sts);
+ intr_sts_masked = intr_sts & ctrl->intr_mask;
+ } while (intr_sts_masked);
+
+ clk_disable_unprepare(ctrl->hclk);
+ return ret;
+}
+
+static bool swrm_wait_for_frame_gen_enabled(struct qcom_swrm_ctrl *ctrl)
+{
+ int retry = SWRM_LINK_STATUS_RETRY_CNT;
+ int comp_sts;
+
+ do {
+ ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_FRAME_GEN_ENABLED],
+ &comp_sts);
+ if (comp_sts & SWRM_FRM_GEN_ENABLED)
+ return true;
+
+ usleep_range(500, 510);
+ } while (retry--);
+
+ dev_err(ctrl->dev, "%s: link status not %s\n", __func__,
+ comp_sts & SWRM_FRM_GEN_ENABLED ? "connected" : "disconnected");
+
+ return false;
+}
+
+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 */
+ if (ctrl->version < SWRM_VERSION_2_0_0)
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_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);
+
+ if (ctrl->version == SWRM_VERSION_1_7_0) {
+ ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU);
+ ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL,
+ SWRM_MCP_BUS_CLK_START << SWRM_EE_CPU);
+ } else if (ctrl->version >= SWRM_VERSION_2_0_0) {
+ ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU);
+ ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL,
+ SWRM_V2_0_CLK_CTRL_CLK_START);
+ } else {
+ 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 >= SWRM_VERSION_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);
+ }
+
+ /* COMP Enable */
+ ctrl->reg_write(ctrl, SWRM_COMP_CFG_ADDR, SWRM_COMP_CFG_ENABLE_MSK);
+
+ /* Set IRQ to PULSE */
+ ctrl->reg_write(ctrl, SWRM_COMP_CFG_ADDR,
+ SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_MSK);
+
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CLEAR],
+ 0xFFFFFFFF);
+
+ /* enable CPU IRQs */
+ if (ctrl->mmio) {
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
+ SWRM_INTERRUPT_STATUS_RMSK);
+ }
+
+ /* 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);
+
+ swrm_wait_for_frame_gen_enabled(ctrl);
+ 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 int qcom_swrm_read_prop(struct sdw_bus *bus)
+{
+ struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
+
+ if (ctrl->version >= SWRM_VERSION_2_0_0) {
+ bus->multi_link = true;
+ bus->hw_sync_min_links = 3;
+ }
+
+ 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;) {
+ len = min(msg->len - i, 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 & 0xff;
+
+ ret = ctrl->reg_write(ctrl, reg, value);
+ if (ret)
+ goto err;
+
+ if (pcfg->si > 0xff) {
+ value = (pcfg->si >> 8) & 0xff;
+ reg = SWRM_DP_SAMPLECTRL2_BANK(params->port_num, bank);
+ 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 = {
+ .read_prop = qcom_swrm_read_prop,
+ .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 sdw_stream_runtime *stream)
+{
+ 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) {
+ port_mask = &ctrl->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 maxport, pn, nports = 0, ret = 0;
+ unsigned int m_port;
+
+ if (direction == SNDRV_PCM_STREAM_CAPTURE)
+ sconfig.direction = SDW_DATA_DIR_TX;
+ else
+ sconfig.direction = SDW_DATA_DIR_RX;
+
+ /* hw parameters will be ignored as we only support PDM */
+ sconfig.ch_count = 1;
+ sconfig.frame_rate = params_rate(params);
+ sconfig.type = stream->type;
+ sconfig.bps = 1;
+
+ mutex_lock(&ctrl->port_lock);
+ list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+ /*
+ * For streams with multiple masters:
+ * Allocate ports only for devices connected to this master.
+ * Such devices will have ports allocated by their own master
+ * and its qcom_swrm_stream_alloc_ports() call.
+ */
+ if (ctrl->bus.id != m_rt->bus->id)
+ continue;
+
+ port_mask = &ctrl->port_mask;
+ maxport = ctrl->num_dout_ports + ctrl->num_din_ports;
+
+
+ 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 out;
+ }
+ set_bit(pn, port_mask);
+ pconfig[nports].num = pn;
+ pconfig[nports].ch_mask = p_rt->ch_mask;
+ nports++;
+ }
+ }
+ }
+
+ sdw_stream_add_master(&ctrl->bus, &sconfig, pconfig,
+ nports, stream);
+out:
+ 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);
+ int ret;
+
+ ret = pm_runtime_get_sync(ctrl->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(ctrl->dev,
+ "pm_runtime_get_sync failed in %s, ret %d\n",
+ __func__, ret);
+ pm_runtime_put_noidle(ctrl->dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+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);
+
+ swrm_wait_for_wr_fifo_done(ctrl);
+ 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];
+ u16 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;
+ bool si_16 = false;
+
+ 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->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",
+ (u8 *)si, nports);
+ if (ret) {
+ ret = of_property_read_u16_array(np, "qcom,ports-sinterval",
+ si, nports);
+ if (ret)
+ return ret;
+ si_16 = true;
+ }
+
+ ret = of_property_read_u8_array(np, "qcom,ports-block-pack-mode",
+ bp_mode, nports);
+ if (ret) {
+ if (ctrl->version <= SWRM_VERSION_1_3_0)
+ 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 */
+ if (si_16)
+ ctrl->pconfig[i + 1].si = si[i];
+ else
+ ctrl->pconfig[i + 1].si = ((u8 *)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 *ctrl = s_file->private;
+ int reg, reg_val, ret;
+
+ ret = pm_runtime_get_sync(ctrl->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(ctrl->dev,
+ "pm_runtime_get_sync failed in %s, ret %d\n",
+ __func__, ret);
+ pm_runtime_put_noidle(ctrl->dev);
+ return ret;
+ }
+
+ for (reg = 0; reg <= ctrl->max_reg; reg += 4) {
+ ctrl->reg_read(ctrl, reg, &reg_val);
+ seq_printf(s_file, "0x%.3x: 0x%.2x\n", reg, reg_val);
+ }
+ pm_runtime_mark_last_busy(ctrl->dev);
+ pm_runtime_put_autosuspend(ctrl->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->max_reg = data->max_reg;
+ ctrl->reg_layout = data->reg_layout;
+ 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_optional_exclusive(dev, "swr_audio_cgcr");
+ if (IS_ERR(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 = dev_err_probe(dev, PTR_ERR(ctrl->hclk), "unable to get iface clock\n");
+ 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;
+ }
+ }
+
+ ctrl->bus.controller_id = -1;
+
+ if (ctrl->version > SWRM_VERSION_1_3_0) {
+ ctrl->reg_read(ctrl, SWRM_COMP_MASTER_ID, &val);
+ ctrl->bus.controller_id = val;
+ }
+
+ 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_dbg(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);
+
+#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 void 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);
+}
+
+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);
+
+ if (ctrl->version == SWRM_VERSION_1_7_0) {
+ ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU);
+ ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL,
+ SWRM_MCP_BUS_CLK_START << SWRM_EE_CPU);
+ } else if (ctrl->version >= SWRM_VERSION_2_0_0) {
+ ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU);
+ ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL,
+ SWRM_V2_0_CLK_CTRL_CLK_START);
+ } else {
+ ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START);
+ }
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CLEAR],
+ SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET);
+
+ ctrl->intr_mask |= SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
+ if (ctrl->version < SWRM_VERSION_2_0_0)
+ ctrl->reg_write(ctrl,
+ ctrl->reg_layout[SWRM_REG_INTERRUPT_MASK_ADDR],
+ ctrl->intr_mask);
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_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;
+
+ swrm_wait_for_wr_fifo_done(ctrl);
+ if (!ctrl->clock_stop_not_supported) {
+ /* Mask bus clash interrupt */
+ ctrl->intr_mask &= ~SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
+ if (ctrl->version < SWRM_VERSION_2_0_0)
+ ctrl->reg_write(ctrl,
+ ctrl->reg_layout[SWRM_REG_INTERRUPT_MASK_ADDR],
+ ctrl->intr_mask);
+ ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_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 },
+ { .compatible = "qcom,soundwire-v1.7.0", .data = &swrm_v1_5_data },
+ { .compatible = "qcom,soundwire-v2.0.0", .data = &swrm_v2_0_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
index ac103bd0c176..3d4d00188c26 100644
--- a/drivers/soundwire/slave.c
+++ b/drivers/soundwire/slave.c
@@ -2,22 +2,34 @@
// 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 <sound/sdca.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);
+ of_node_put(slave->dev.of_node);
+ mutex_destroy(&slave->sdw_dev_lock);
kfree(slave);
}
-static int sdw_slave_add(struct sdw_bus *bus,
- struct sdw_slave_id *id, struct fwnode_handle *fwnode)
+const 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)
@@ -28,21 +40,49 @@ static int sdw_slave_add(struct sdw_bus *bus,
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);
+ if (id->unique_id == SDW_IGNORED_UNIQUE_ID) {
+ /* name shall be sdw:ctrl:link:mfg:part:class */
+ dev_set_name(&slave->dev, "sdw:%01x:%01x:%04x:%04x:%02x",
+ bus->controller_id, bus->link_id, id->mfg_id, id->part_id,
+ id->class_id);
+ } else {
+ /* name shall be sdw:ctrl:link:mfg:part:class:unique */
+ dev_set_name(&slave->dev, "sdw:%01x:%01x:%04x:%04x:%02x:%01x",
+ bus->controller_id, 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->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);
+ /*
+ * The Soundwire driver probe may optionally register SDCA
+ * sub-devices, one per Function. This means the information
+ * on the SDCA revision and the number/type of Functions need
+ * to be extracted from platform firmware before the SoundWire
+ * driver probe, and as a consequence before the SoundWire
+ * device_register() below.
+ */
+ sdca_lookup_interface_revision(slave);
+ sdca_lookup_functions(slave);
+
ret = device_register(&slave->dev);
if (ret) {
dev_err(bus->dev, "Failed to add slave: ret %d\n", ret);
@@ -55,12 +95,112 @@ static int sdw_slave_add(struct sdw_bus *bus,
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)
+{
+ unsigned int link_id;
+ u64 addr;
+ int ret;
+
+ ret = acpi_get_local_u64_address(adev->handle, &addr);
+ if (ret < 0)
+ 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
@@ -69,7 +209,7 @@ static int sdw_slave_add(struct sdw_bus *bus,
*/
int sdw_acpi_find_slaves(struct sdw_bus *bus)
{
- struct acpi_device *adev, *parent;
+ struct acpi_device *parent;
parent = ACPI_COMPANION(bus->dev);
if (!parent) {
@@ -77,38 +217,66 @@ int sdw_acpi_find_slaves(struct sdw_bus *bus)
return -ENODEV;
}
- list_for_each_entry(adev, &parent->children, node) {
- unsigned long long addr;
+ 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;
- unsigned int link_id;
- acpi_status status;
+ const __be32 *addr;
+
+ compat = of_get_property(node, "compatible", NULL);
+ if (!compat)
+ continue;
- status = acpi_evaluate_integer(adev->handle,
- METHOD_NAME__ADR, NULL, &addr);
+ ret = sscanf(compat, "sdw%01x%04hx%04hx%02hhx", &sdw_version,
+ &id.mfg_id, &id.part_id, &id.class_id);
- if (ACPI_FAILURE(status)) {
- dev_err(bus->dev, "_ADR resolution failed: %x\n",
- status);
- return status;
+ 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;
}
- /* Extract link id from ADR, Bit 51 to 48 (included) */
- link_id = (addr >> 48) & GENMASK(3, 0);
+ 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_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));
+ sdw_slave_add(bus, &id, of_fwnode_handle(node));
}
return 0;
}
-#endif
+struct device *of_sdw_find_device_by_node(struct device_node *np)
+{
+ return bus_find_device_by_of_node(&sdw_bus_type, np);
+}
+EXPORT_SYMBOL_GPL(of_sdw_find_device_by_node);
+
+MODULE_IMPORT_NS("SND_SOC_SDCA");
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c
index bd879b1a76c8..38c9dbd35606 100644
--- a/drivers/soundwire/stream.c
+++ b/drivers/soundwire/stream.c
@@ -13,6 +13,9 @@
#include <linux/slab.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <linux/string_choices.h>
+#include <sound/soc.h>
#include "bus.h"
/*
@@ -21,41 +24,46 @@
* 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 rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147,
+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 cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16};
+int sdw_cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16};
+EXPORT_SYMBOL(sdw_cols);
-static int sdw_find_col_index(int col)
+int sdw_find_col_index(int col)
{
int i;
for (i = 0; i < SDW_FRAME_COLS; i++) {
- if (cols[i] == col)
+ 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);
-static int sdw_find_row_index(int row)
+int sdw_find_row_index(int row)
{
int i;
for (i = 0; i < SDW_FRAME_ROWS; i++) {
- if (rows[i] == row)
+ 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)
+ struct sdw_slave *slave,
+ struct sdw_transport_params *t_params,
+ enum sdw_dpn_type type)
{
u32 addr1, addr2, addr3, addr4;
int ret;
@@ -74,67 +82,80 @@ static int _sdw_program_slave_port_params(struct sdw_bus *bus,
}
/* Program DPN_OffsetCtrl2 registers */
- ret = sdw_write(slave, addr1, t_params->offset2);
+ ret = sdw_write_no_pm(slave, addr1, t_params->offset2);
if (ret < 0) {
- dev_err(bus->dev, "DPN_OffsetCtrl2 register write failed");
+ 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");
- return ret;
+ /* DP0 does not implement BlockCtrl3 */
+ if (t_params->port_num) {
+ /* Program DPN_BlockCtrl3 register */
+ ret = sdw_write_no_pm(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 and beyond this point only FULL is
+ * 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 = (t_params->sample_interval - 1);
- wbuf &= SDW_DPN_SAMPLECTRL_HIGH;
- wbuf >>= SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH);
+ wbuf = FIELD_GET(SDW_DPN_SAMPLECTRL_HIGH, t_params->sample_interval - 1);
- ret = sdw_write(slave, addr3, wbuf);
+ ret = sdw_write_no_pm(slave, addr3, wbuf);
if (ret < 0) {
- dev_err(bus->dev, "DPN_SampleCtrl2 register write failed");
+ dev_err(bus->dev, "DPN_SampleCtrl2 register write failed\n");
return ret;
}
/* Program DPN_HCtrl register */
- wbuf = t_params->hstart;
- wbuf <<= SDW_REG_SHIFT(SDW_DPN_HCTRL_HSTART);
- wbuf |= t_params->hstop;
+ wbuf = FIELD_PREP(SDW_DPN_HCTRL_HSTART, t_params->hstart);
+ wbuf |= FIELD_PREP(SDW_DPN_HCTRL_HSTOP, t_params->hstop);
- ret = sdw_write(slave, addr4, wbuf);
+ ret = sdw_write_no_pm(slave, addr4, wbuf);
if (ret < 0)
- dev_err(bus->dev, "DPN_HCtrl register write failed");
+ 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_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;
+ enum sdw_dpn_type port_type;
+ bool read_only_wordlength;
int ret;
u8 wbuf;
- dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave,
- s_rt->direction,
- t_params->port_num);
- if (!dpn_prop)
- return -EINVAL;
+ if (s_rt->slave->is_mockup_device)
+ return 0;
+
+ if (t_params->port_num) {
+ struct sdw_dpn_prop *dpn_prop;
+
+ dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, s_rt->direction,
+ t_params->port_num);
+ if (!dpn_prop)
+ return -EINVAL;
+
+ read_only_wordlength = dpn_prop->read_only_wordlength;
+ port_type = dpn_prop->type;
+ } else {
+ read_only_wordlength = false;
+ port_type = SDW_DPN_FULL;
+ }
addr1 = SDW_DPN_PORTCTRL(t_params->port_num);
addr2 = SDW_DPN_BLOCKCTRL1(t_params->port_num);
@@ -153,51 +174,53 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus,
}
/* Program DPN_PortCtrl register */
- wbuf = p_params->data_mode << SDW_REG_SHIFT(SDW_DPN_PORTCTRL_DATAMODE);
- wbuf |= p_params->flow_mode;
+ wbuf = FIELD_PREP(SDW_DPN_PORTCTRL_DATAMODE, p_params->data_mode);
+ wbuf |= FIELD_PREP(SDW_DPN_PORTCTRL_FLOWMODE, p_params->flow_mode);
- ret = sdw_update(s_rt->slave, addr1, 0xF, wbuf);
+ ret = sdw_update_no_pm(s_rt->slave, addr1, 0xF, wbuf);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
- "DPN_PortCtrl register write failed for port %d",
+ "DPN_PortCtrl register write failed for port %d\n",
t_params->port_num);
return ret;
}
- /* 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",
- t_params->port_num);
- return ret;
+ if (!read_only_wordlength) {
+ /* Program DPN_BlockCtrl1 register */
+ ret = sdw_write_no_pm(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);
+ ret = sdw_write_no_pm(s_rt->slave, addr3, wbuf);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
- "DPN_SampleCtrl1 register write failed for port %d",
+ "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);
+ ret = sdw_write_no_pm(s_rt->slave, addr4, t_params->offset1);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
- "DPN_OffsetCtrl1 register write failed for port %d",
+ "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);
+ ret = sdw_write_no_pm(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",
+ "DPN_BlockCtrl2 reg write failed for port %d\n",
t_params->port_num);
return ret;
}
@@ -205,21 +228,21 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus,
/* program DPN_LaneCtrl register */
if (slave_prop->lane_control_support) {
- ret = sdw_write(s_rt->slave, addr6, t_params->lane_ctrl);
+ ret = sdw_write_no_pm(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",
+ "DPN_LaneCtrl register write failed for port %d\n",
t_params->port_num);
return ret;
}
}
- if (dpn_prop->type != SDW_DPN_SIMPLE) {
+ if (port_type != SDW_DPN_SIMPLE) {
ret = _sdw_program_slave_port_params(bus, s_rt->slave,
- t_params, dpn_prop->type);
+ t_params, port_type);
if (ret < 0)
dev_err(&s_rt->slave->dev,
- "Transport reg write failed for port: %d",
+ "Transport reg write failed for port: %d\n",
t_params->port_num);
}
@@ -227,13 +250,13 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus,
}
static int sdw_program_master_port_params(struct sdw_bus *bus,
- struct sdw_port_runtime *p_rt)
+ struct sdw_port_runtime *p_rt)
{
int ret;
/*
* we need to set transport and port parameters for the port.
- * Transport parameters refers to the smaple interval, offsets and
+ * 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
*/
@@ -244,8 +267,8 @@ static int sdw_program_master_port_params(struct sdw_bus *bus,
return ret;
return bus->port_ops->dpn_set_port_params(bus,
- &p_rt->port_params,
- bus->params.next_bank);
+ &p_rt->port_params,
+ bus->params.next_bank);
}
/**
@@ -256,7 +279,7 @@ static int sdw_program_master_port_params(struct sdw_bus *bus,
*/
static int sdw_program_port_params(struct sdw_master_runtime *m_rt)
{
- struct sdw_slave_runtime *s_rt = NULL;
+ struct sdw_slave_runtime *s_rt;
struct sdw_bus *bus = m_rt->bus;
struct sdw_port_runtime *p_rt;
int ret = 0;
@@ -292,8 +315,9 @@ static int sdw_program_port_params(struct sdw_master_runtime *m_rt)
* 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_slave_runtime *s_rt,
+ struct sdw_port_runtime *p_rt,
+ bool en)
{
struct sdw_transport_params *t_params = &p_rt->transport_params;
u32 addr;
@@ -309,25 +333,26 @@ static int sdw_enable_disable_slave_ports(struct sdw_bus *bus,
* it is safe to reset this register
*/
if (en)
- ret = sdw_update(s_rt->slave, addr, 0xFF, p_rt->ch_mask);
+ ret = sdw_write_no_pm(s_rt->slave, addr, p_rt->ch_mask);
else
- ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0);
+ ret = sdw_write_no_pm(s_rt->slave, addr, 0x0);
if (ret < 0)
dev_err(&s_rt->slave->dev,
- "Slave chn_en reg write failed:%d port:%d",
+ "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_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 = 0;
+ int ret;
enable_ch.port_num = p_rt->num;
enable_ch.ch_mask = p_rt->ch_mask;
@@ -336,17 +361,18 @@ static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt,
/* 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);
+ &enable_ch,
+ bus->params.next_bank);
if (ret < 0) {
dev_err(bus->dev,
- "Master chn_en write failed:%d port:%d",
+ "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");
+ str_enable_disable(en));
return -EINVAL;
}
@@ -363,14 +389,14 @@ static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt,
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 = NULL;
+ 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);
+ s_port, en);
if (ret < 0)
return ret;
}
@@ -387,31 +413,42 @@ static int sdw_enable_disable_ports(struct sdw_master_runtime *m_rt, bool en)
}
static int sdw_do_port_prep(struct sdw_slave_runtime *s_rt,
- struct sdw_prepare_ch prep_ch, enum sdw_port_prep_ops cmd)
+ struct sdw_prepare_ch prep_ch,
+ enum sdw_port_prep_ops cmd)
{
- const struct sdw_slave_ops *ops = s_rt->slave->ops;
- int ret;
+ int ret = 0;
+ struct sdw_slave *slave = s_rt->slave;
- if (ops->port_prep) {
- ret = ops->port_prep(s_rt->slave, &prep_ch, cmd);
- if (ret < 0) {
- dev_err(&s_rt->slave->dev,
- "Slave Port Prep cmd %d failed: %d", cmd, ret);
- return ret;
+ 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);
}
}
- return 0;
+ 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 sdw_slave_runtime *s_rt,
+ struct sdw_port_runtime *p_rt,
+ bool prep)
{
- struct completion *port_ready = NULL;
+ struct completion *port_ready;
struct sdw_dpn_prop *dpn_prop;
struct sdw_prepare_ch prep_ch;
- unsigned int time_left;
+ u32 imp_def_interrupts;
+ bool simple_ch_prep_sm;
+ u32 ch_prep_timeout;
bool intr = false;
int ret = 0, val;
u32 addr;
@@ -419,20 +456,36 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
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", prep_ch.num);
- return -EINVAL;
+ if (p_rt->num) {
+ 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;
+ }
+
+ imp_def_interrupts = dpn_prop->imp_def_interrupts;
+ simple_ch_prep_sm = dpn_prop->simple_ch_prep_sm;
+ ch_prep_timeout = dpn_prop->ch_prep_timeout;
+ } else {
+ struct sdw_dp0_prop *dp0_prop = s_rt->slave->prop.dp0_prop;
+
+ if (!dp0_prop) {
+ dev_err(bus->dev,
+ "Slave DP0 properties not found\n");
+ return -EINVAL;
+ }
+ imp_def_interrupts = dp0_prop->imp_def_interrupts;
+ simple_ch_prep_sm = dp0_prop->simple_ch_prep_sm;
+ ch_prep_timeout = dp0_prop->ch_prep_timeout;
}
prep_ch.prepare = prep;
prep_ch.bank = bus->params.next_bank;
- if (dpn_prop->device_interrupts || !dpn_prop->simple_ch_prep_sm)
+ if (imp_def_interrupts || !simple_ch_prep_sm ||
+ bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL)
intr = true;
/*
@@ -442,57 +495,57 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
*/
if (prep && intr) {
ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
- dpn_prop->device_interrupts);
+ 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);
+ sdw_do_port_prep(s_rt, prep_ch, prep ? SDW_OPS_PORT_PRE_PREP : SDW_OPS_PORT_PRE_DEPREP);
/* Prepare Slave port implementing CP_SM */
- if (!dpn_prop->simple_ch_prep_sm) {
+ if (!simple_ch_prep_sm) {
addr = SDW_DPN_PREPARECTRL(p_rt->num);
if (prep)
- ret = sdw_update(s_rt->slave, addr,
- 0xFF, p_rt->ch_mask);
+ ret = sdw_write_no_pm(s_rt->slave, addr, p_rt->ch_mask);
else
- ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0);
+ ret = sdw_write_no_pm(s_rt->slave, addr, 0x0);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
- "Slave prep_ctrl reg write failed");
+ "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];
- time_left = wait_for_completion_timeout(port_ready,
- msecs_to_jiffies(dpn_prop->ch_prep_timeout));
+ wait_for_completion_timeout(port_ready,
+ msecs_to_jiffies(ch_prep_timeout));
- val = sdw_read(s_rt->slave, SDW_DPN_PREPARESTATUS(p_rt->num));
- val &= p_rt->ch_mask;
- if (!time_left || val) {
+ val = sdw_read_no_pm(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", prep_ch.num);
- return -ETIMEDOUT;
+ "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);
+ sdw_do_port_prep(s_rt, prep_ch, prep ? SDW_OPS_PORT_POST_PREP : SDW_OPS_PORT_POST_DEPREP);
/* Disable interrupt after Port de-prepare */
if (!prep && intr)
ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
- dpn_prop->device_interrupts);
+ 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_port_runtime *p_rt,
+ bool prep)
{
struct sdw_transport_params *t_params = &p_rt->transport_params;
struct sdw_bus *bus = m_rt->bus;
@@ -509,8 +562,8 @@ static int sdw_prep_deprep_master_ports(struct sdw_master_runtime *m_rt,
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",
- t_params->port_num);
+ dev_err(bus->dev, "Port prepare failed for port:%d\n",
+ t_params->port_num);
return ret;
}
}
@@ -527,7 +580,7 @@ static int sdw_prep_deprep_master_ports(struct sdw_master_runtime *m_rt,
*/
static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep)
{
- struct sdw_slave_runtime *s_rt = NULL;
+ struct sdw_slave_runtime *s_rt;
struct sdw_port_runtime *p_rt;
int ret = 0;
@@ -535,7 +588,7 @@ static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep)
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);
+ p_rt, prep);
if (ret < 0)
return ret;
}
@@ -564,7 +617,7 @@ 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 = 0;
+ int ret;
if (bus->ops->set_bus_conf) {
ret = bus->ops->set_bus_conf(bus, &bus->params);
@@ -575,16 +628,27 @@ static int sdw_notify_config(struct sdw_master_runtime *m_rt)
list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
slave = s_rt->slave;
- if (slave->ops->bus_config) {
- ret = slave->ops->bus_config(slave, &bus->params);
- if (ret < 0)
- dev_err(bus->dev, "Notify Slave: %d failed",
- slave->dev_num);
- return ret;
+ 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 ret;
+ return 0;
}
/**
@@ -592,23 +656,72 @@ static int sdw_notify_config(struct sdw_master_runtime *m_rt)
* 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)
+static int sdw_program_params(struct sdw_bus *bus, bool prepare)
{
- struct sdw_master_runtime *m_rt = NULL;
+ struct sdw_master_runtime *m_rt;
+ struct sdw_slave *slave;
int ret = 0;
+ u32 addr1;
+ /* Check if all Peripherals comply with SDCA */
+ list_for_each_entry(slave, &bus->slaves, node) {
+ if (!slave->dev_num_sticky)
+ continue;
+ if (!is_clock_scaling_supported_by_slave(slave)) {
+ dev_dbg(&slave->dev, "The Peripheral doesn't comply with SDCA\n");
+ goto manager_runtime;
+ }
+ }
+
+ if (bus->params.next_bank)
+ addr1 = SDW_SCP_BUSCLOCK_SCALE_B1;
+ else
+ addr1 = SDW_SCP_BUSCLOCK_SCALE_B0;
+
+ /* Program SDW_SCP_BUSCLOCK_SCALE if all Peripherals comply with SDCA */
+ list_for_each_entry(slave, &bus->slaves, node) {
+ int scale_index;
+ u8 base;
+
+ if (!slave->dev_num_sticky)
+ continue;
+ scale_index = sdw_slave_get_scale_index(slave, &base);
+ if (scale_index < 0)
+ return scale_index;
+
+ ret = sdw_write_no_pm(slave, addr1, scale_index);
+ if (ret < 0) {
+ dev_err(&slave->dev, "SDW_SCP_BUSCLOCK_SCALE register write failed\n");
+ return ret;
+ }
+ }
+
+manager_runtime:
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", ret);
+ "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", ret);
+ dev_err(bus->dev,
+ "Notify bus config failed: %d\n", ret);
return ret;
}
@@ -618,7 +731,7 @@ static int sdw_program_params(struct sdw_bus *bus)
ret = sdw_enable_disable_ports(m_rt, true);
if (ret < 0) {
- dev_err(bus->dev, "Enable channel failed: %d", ret);
+ dev_err(bus->dev, "Enable channel failed: %d\n", ret);
return ret;
}
}
@@ -631,16 +744,14 @@ 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 = NULL;
- int ret = 0;
+ 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;
@@ -658,28 +769,28 @@ static int sdw_bank_switch(struct sdw_bus *bus, int m_rt_count)
addr = SDW_SCP_FRAMECTRL_B0;
sdw_fill_msg(wr_msg, NULL, addr, 1, SDW_BROADCAST_DEV_NUM,
- SDW_MSG_FLAG_WRITE, wbuf);
+ SDW_MSG_FLAG_WRITE, wbuf);
wr_msg->ssp_sync = true;
/*
* Set the multi_link flag only when both the hardware supports
- * and there is a stream handled by multiple masters
+ * and hardware-based sync is required
*/
- multi_link = bus->multi_link && (m_rt_count > 1);
+ multi_link = bus->multi_link && (m_rt_count >= bus->hw_sync_min_links);
if (multi_link)
- ret = sdw_transfer_defer(bus, wr_msg, &bus->defer_msg);
+ ret = sdw_transfer_defer(bus, wr_msg);
else
ret = sdw_transfer(bus, wr_msg);
- if (ret < 0) {
- dev_err(bus->dev, "Slave frame_ctrl reg write failed");
+ 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);
+ kfree(wr_msg);
bus->defer_msg.msg = NULL;
bus->params.curr_bank = !bus->params.curr_bank;
bus->params.next_bank = !bus->params.next_bank;
@@ -691,6 +802,7 @@ error:
kfree(wbuf);
error_1:
kfree(wr_msg);
+ bus->defer_msg.msg = NULL;
return ret;
}
@@ -698,14 +810,15 @@ error_1:
* 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)
+static int sdw_ml_sync_bank_switch(struct sdw_bus *bus, bool multi_link)
{
unsigned long time_left;
- if (!bus->multi_link)
+ if (!multi_link)
return 0;
/* Wait for completion of transfer */
@@ -713,7 +826,7 @@ static int sdw_ml_sync_bank_switch(struct sdw_bus *bus)
bus->bank_switch_timeout);
if (!time_left) {
- dev_err(bus->dev, "Controller Timed out on bank switch");
+ dev_err(bus->dev, "Controller Timed out on bank switch\n");
return -ETIMEDOUT;
}
@@ -723,6 +836,7 @@ static int sdw_ml_sync_bank_switch(struct sdw_bus *bus)
if (bus->defer_msg.msg) {
kfree(bus->defer_msg.msg->buf);
kfree(bus->defer_msg.msg);
+ bus->defer_msg.msg = NULL;
}
return 0;
@@ -730,17 +844,20 @@ static int sdw_ml_sync_bank_switch(struct sdw_bus *bus)
static int do_bank_switch(struct sdw_stream_runtime *stream)
{
- struct sdw_master_runtime *m_rt = NULL;
+ struct sdw_master_runtime *m_rt;
const struct sdw_master_ops *ops;
- struct sdw_bus *bus = NULL;
+ struct sdw_bus *bus;
bool multi_link = false;
+ int m_rt_count;
int ret = 0;
+ m_rt_count = stream->m_rt_count;
+
list_for_each_entry(m_rt, &stream->master_list, stream_node) {
bus = m_rt->bus;
ops = bus->ops;
- if (bus->multi_link) {
+ if (bus->multi_link && m_rt_count >= bus->hw_sync_min_links) {
multi_link = true;
mutex_lock(&bus->msg_lock);
}
@@ -750,7 +867,7 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
ret = ops->pre_bank_switch(bus);
if (ret < 0) {
dev_err(bus->dev,
- "Pre bank switch op failed: %d", ret);
+ "Pre bank switch op failed: %d\n", ret);
goto msg_unlock;
}
}
@@ -761,11 +878,10 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
* synchronized across all Masters and happens later as a
* part of post_bank_switch ops.
*/
- ret = sdw_bank_switch(bus, stream->m_rt_count);
+ ret = sdw_bank_switch(bus, m_rt_count);
if (ret < 0) {
- dev_err(bus->dev, "Bank switch failed: %d", ret);
+ dev_err(bus->dev, "Bank switch failed: %d\n", ret);
goto error;
-
}
}
@@ -784,12 +900,14 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
ret = ops->post_bank_switch(bus);
if (ret < 0) {
dev_err(bus->dev,
- "Post bank switch op failed: %d", ret);
+ "Post bank switch op failed: %d\n",
+ ret);
goto error;
}
- } else if (bus->multi_link && stream->m_rt_count > 1) {
+ } else if (multi_link) {
dev_err(bus->dev,
- "Post bank switch ops not implemented");
+ "Post bank switch ops not implemented\n");
+ ret = -EINVAL;
goto error;
}
@@ -798,25 +916,27 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
bus->bank_switch_timeout = DEFAULT_BANK_SWITCH_TIMEOUT;
/* Check if bank switch was successful */
- ret = sdw_ml_sync_bank_switch(bus);
+ ret = sdw_ml_sync_bank_switch(bus, multi_link);
if (ret < 0) {
dev_err(bus->dev,
- "multi link bank switch failed: %d", ret);
+ "multi link bank switch failed: %d\n", ret);
goto error;
}
- mutex_unlock(&bus->msg_lock);
+ 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;
-
- kfree(bus->defer_msg.msg->buf);
- kfree(bus->defer_msg.msg);
+ if (bus->defer_msg.msg) {
+ kfree(bus->defer_msg.msg->buf);
+ kfree(bus->defer_msg.msg);
+ bus->defer_msg.msg = NULL;
+ }
}
msg_unlock:
@@ -832,278 +952,377 @@ msg_unlock:
return ret;
}
-/**
- * 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)
+static struct sdw_port_runtime *sdw_port_alloc(struct list_head *port_list)
{
- kfree(stream);
+ 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;
}
-EXPORT_SYMBOL(sdw_release_stream);
-/**
- * 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(char *stream_name)
+static int sdw_port_config(struct sdw_port_runtime *p_rt,
+ const struct sdw_port_config *port_config,
+ int port_index)
{
- struct sdw_stream_runtime *stream;
+ p_rt->ch_mask = port_config[port_index].ch_mask;
+ p_rt->num = port_config[port_index].num;
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return NULL;
+ /*
+ * TODO: Check port capabilities for requested configuration
+ */
- stream->name = stream_name;
- INIT_LIST_HEAD(&stream->master_list);
- stream->state = SDW_STREAM_ALLOCATED;
- stream->m_rt_count = 0;
+ return 0;
+}
- return stream;
+static void sdw_port_free(struct sdw_port_runtime *p_rt)
+{
+ list_del(&p_rt->port_node);
+ kfree(p_rt);
}
-EXPORT_SYMBOL(sdw_alloc_stream);
-static struct sdw_master_runtime
-*sdw_find_master_rt(struct sdw_bus *bus,
- struct sdw_stream_runtime *stream)
+static bool sdw_slave_port_allocated(struct sdw_slave_runtime *s_rt)
{
- struct sdw_master_runtime *m_rt = NULL;
+ 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;
- /* 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;
+ 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);
+ }
+ }
}
+}
- return NULL;
+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;
}
-/**
- * sdw_alloc_master_rt() - Allocates and initialize Master runtime handle
- *
- * @bus: SDW bus instance
- * @stream_config: Stream configuration
- * @stream: Stream runtime handle.
- *
- * This function is to be called with bus_lock held.
- */
-static struct sdw_master_runtime
-*sdw_alloc_master_rt(struct sdw_bus *bus,
- struct sdw_stream_config *stream_config,
- struct sdw_stream_runtime *stream)
+static int sdw_slave_port_is_valid_range(struct device *dev, int num)
{
- struct sdw_master_runtime *m_rt;
+ if (!SDW_VALID_PORT_RANGE(num)) {
+ dev_err(dev, "SoundWire: Invalid port number :%d\n", num);
+ return -EINVAL;
+ }
- /*
- * check if Master is already allocated (as a result of Slave adding
- * it first), if so skip allocation and go to configure
- */
- m_rt = sdw_find_master_rt(bus, stream);
- if (m_rt)
- goto stream_config;
+ return 0;
+}
- m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
- if (!m_rt)
- return NULL;
+static int sdw_slave_port_config(struct sdw_slave *slave,
+ struct sdw_slave_runtime *s_rt,
+ const struct sdw_port_config *port_config,
+ bool is_bpt_stream)
+{
+ struct sdw_port_runtime *p_rt;
+ int ret;
+ int i;
- /* 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);
+ i = 0;
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ /*
+ * TODO: Check valid port range as defined by DisCo/
+ * slave
+ */
+ if (!is_bpt_stream) {
+ ret = sdw_slave_port_is_valid_range(&slave->dev, port_config[i].num);
+ if (ret < 0)
+ return ret;
+ } else if (port_config[i].num) {
+ return -EINVAL;
+ }
- list_add_tail(&m_rt->bus_node, &bus->m_rt_list);
+ ret = sdw_port_config(p_rt, port_config, i);
+ if (ret < 0)
+ return ret;
+ i++;
+ }
-stream_config:
- m_rt->ch_count = stream_config->ch_count;
- m_rt->bus = bus;
- m_rt->stream = stream;
- m_rt->direction = stream_config->direction;
+ return 0;
+}
- return m_rt;
+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,
+ const 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_alloc_slave_rt() - Allocate and initialize Slave runtime handle.
+ * sdw_slave_rt_alloc() - Allocate a Slave runtime handle.
*
* @slave: Slave handle
- * @stream_config: Stream configuration
- * @stream: Stream runtime handle
+ * @m_rt: Master runtime handle
*
* This function is to be called with bus_lock held.
*/
static struct sdw_slave_runtime
-*sdw_alloc_slave_rt(struct sdw_slave *slave,
- struct sdw_stream_config *stream_config,
- struct sdw_stream_runtime *stream)
+*sdw_slave_rt_alloc(struct sdw_slave *slave,
+ struct sdw_master_runtime *m_rt)
{
- struct sdw_slave_runtime *s_rt = NULL;
+ 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->ch_count = stream_config->ch_count;
- s_rt->direction = stream_config->direction;
s_rt->slave = slave;
+ list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list);
+
return s_rt;
}
-static void sdw_master_port_release(struct sdw_bus *bus,
- struct sdw_master_runtime *m_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)
{
- struct sdw_port_runtime *p_rt, *_p_rt;
+ s_rt->ch_count = stream_config->ch_count;
+ s_rt->direction = stream_config->direction;
- list_for_each_entry_safe(p_rt, _p_rt,
- &m_rt->port_list, port_node) {
- list_del(&p_rt->port_node);
- kfree(p_rt);
- }
+ return 0;
}
-static void sdw_slave_port_release(struct sdw_bus *bus,
- struct sdw_slave *slave,
- struct sdw_stream_runtime *stream)
+static struct sdw_slave_runtime *sdw_slave_rt_find(struct sdw_slave *slave,
+ struct sdw_stream_runtime *stream)
{
- struct sdw_port_runtime *p_rt, *_p_rt;
+ struct sdw_slave_runtime *s_rt, *_s_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) {
-
- list_del(&p_rt->port_node);
- kfree(p_rt);
- }
+ /* 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_release_slave_stream() - Free Slave(s) runtime handle
+ * 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_release_slave_stream(struct sdw_slave *slave,
- struct sdw_stream_runtime *stream)
+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_slave_runtime *s_rt, *_s_rt;
struct sdw_master_runtime *m_rt;
+ /* Retrieve Bus handle if already available */
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) {
- list_del(&s_rt->m_rt_node);
- kfree(s_rt);
- return;
- }
- }
+ if (m_rt->bus == bus)
+ return m_rt;
}
+
+ return NULL;
}
/**
- * sdw_release_master_stream() - Free Master runtime handle
+ * sdw_master_rt_alloc() - Allocates a Master runtime handle
*
- * @m_rt: Master runtime node
+ * @bus: SDW bus instance
* @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_release_slave_stream() will have
- * no effect as Slave(s) runtime handle would already be freed up.
+ * This function is to be called with bus_lock held.
*/
-static void sdw_release_master_stream(struct sdw_master_runtime *m_rt,
- struct sdw_stream_runtime *stream)
+static struct sdw_master_runtime
+*sdw_master_rt_alloc(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream)
{
- struct sdw_slave_runtime *s_rt, *_s_rt;
+ struct sdw_master_runtime *m_rt, *walk_m_rt;
+ struct list_head *insert_after;
+
+ if (stream->type == SDW_STREAM_BPT) {
+ if (bus->stream_refcount > 0 || bus->bpt_stream_refcount > 0) {
+ dev_err(bus->dev, "%s: %d/%d audio/BPT stream already allocated\n",
+ __func__, bus->stream_refcount, bus->bpt_stream_refcount);
+ return ERR_PTR(-EBUSY);
+ }
+ } else {
+ if (bus->bpt_stream_refcount > 0) {
+ dev_err(bus->dev, "%s: BPT stream already allocated\n",
+ __func__);
+ return ERR_PTR(-EAGAIN);
+ }
+ }
- list_for_each_entry_safe(s_rt, _s_rt, &m_rt->slave_rt_list, m_rt_node) {
- sdw_slave_port_release(s_rt->slave->bus, s_rt->slave, stream);
- sdw_release_slave_stream(s_rt->slave, stream);
+ 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);
+
+ /*
+ * Add in order of bus id so that when taking the bus_lock
+ * of multiple buses they will always be taken in the same
+ * order to prevent a mutex deadlock.
+ */
+ insert_after = &stream->master_list;
+ list_for_each_entry_reverse(walk_m_rt, &stream->master_list, stream_node) {
+ if (walk_m_rt->bus->id < bus->id) {
+ insert_after = &walk_m_rt->stream_node;
+ break;
+ }
}
+ list_add(&m_rt->stream_node, insert_after);
- list_del(&m_rt->stream_node);
- list_del(&m_rt->bus_node);
- kfree(m_rt);
+ list_add_tail(&m_rt->bus_node, &bus->m_rt_list);
+
+ m_rt->bus = bus;
+ m_rt->stream = stream;
+
+ bus->stream_refcount++;
+ if (stream->type == SDW_STREAM_BPT)
+ bus->bpt_stream_refcount++;
+
+ return m_rt;
}
/**
- * sdw_stream_remove_master() - Remove master from sdw_stream
+ * sdw_master_rt_config() - Configure Master runtime handle
*
- * @bus: SDW Bus instance
- * @stream: SoundWire stream
+ * @m_rt: Master runtime handle
+ * @stream_config: Stream configuration
*
- * This removes and frees port_rt and master_rt from a stream
+ * This function is to be called with bus_lock held.
*/
-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_release(bus, m_rt);
- sdw_release_master_stream(m_rt, stream);
- stream->m_rt_count--;
- }
-
- if (list_empty(&stream->master_list))
- stream->state = SDW_STREAM_RELEASED;
-
- mutex_unlock(&bus->bus_lock);
+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;
}
-EXPORT_SYMBOL(sdw_stream_remove_master);
/**
- * sdw_stream_remove_slave() - Remove slave from sdw_stream
+ * sdw_master_rt_free() - Free Master runtime handle
*
- * @slave: SDW Slave instance
- * @stream: SoundWire stream
+ * @m_rt: Master runtime node
+ * @stream: Stream runtime handle.
*
- * This removes and frees port_rt and slave_rt from a stream
+ * 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.
*/
-int sdw_stream_remove_slave(struct sdw_slave *slave,
- struct sdw_stream_runtime *stream)
+static void sdw_master_rt_free(struct sdw_master_runtime *m_rt,
+ struct sdw_stream_runtime *stream)
{
- mutex_lock(&slave->bus->bus_lock);
+ struct sdw_slave_runtime *s_rt, *_s_rt;
+ struct sdw_bus *bus = m_rt->bus;
- sdw_slave_port_release(slave->bus, slave, stream);
- sdw_release_slave_stream(slave, stream);
+ 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);
+ }
- mutex_unlock(&slave->bus->bus_lock);
+ list_del(&m_rt->stream_node);
+ list_del(&m_rt->bus_node);
+ kfree(m_rt);
- return 0;
+ if (stream->type == SDW_STREAM_BPT)
+ bus->bpt_stream_refcount--;
+ bus->stream_refcount--;
}
-EXPORT_SYMBOL(sdw_stream_remove_slave);
/**
* sdw_config_stream() - Configure the allocated stream
@@ -1116,8 +1335,9 @@ EXPORT_SYMBOL(sdw_stream_remove_slave);
* 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)
+ struct sdw_stream_runtime *stream,
+ struct sdw_stream_config *stream_config,
+ bool is_slave)
{
/*
* Update the stream rate, channel and bps based on data
@@ -1128,14 +1348,14 @@ static int sdw_config_stream(struct device *dev,
* 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", stream->name);
+ 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", stream->name);
+ stream->params.bps != stream_config->bps) {
+ dev_err(dev, "bps not matching, stream:%s\n", stream->name);
return -EINVAL;
}
@@ -1150,233 +1370,6 @@ static int sdw_config_stream(struct device *dev,
return 0;
}
-static int sdw_is_valid_port_range(struct device *dev,
- struct sdw_port_runtime *p_rt)
-{
- if (!SDW_VALID_PORT_RANGE(p_rt->num)) {
- dev_err(dev,
- "SoundWire: Invalid port number :%d", p_rt->num);
- return -EINVAL;
- }
-
- return 0;
-}
-
-static struct sdw_port_runtime *sdw_port_alloc(struct device *dev,
- struct sdw_port_config *port_config,
- int port_index)
-{
- struct sdw_port_runtime *p_rt;
-
- p_rt = kzalloc(sizeof(*p_rt), GFP_KERNEL);
- if (!p_rt)
- return NULL;
-
- p_rt->ch_mask = port_config[port_index].ch_mask;
- p_rt->num = port_config[port_index].num;
-
- return p_rt;
-}
-
-static int sdw_master_port_config(struct sdw_bus *bus,
- struct sdw_master_runtime *m_rt,
- struct sdw_port_config *port_config,
- 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(bus->dev, port_config, i);
- if (!p_rt)
- return -ENOMEM;
-
- /*
- * TODO: Check port capabilities for requested
- * configuration (audio mode support)
- */
-
- list_add_tail(&p_rt->port_node, &m_rt->port_list);
- }
-
- return 0;
-}
-
-static int sdw_slave_port_config(struct sdw_slave *slave,
- struct sdw_slave_runtime *s_rt,
- struct sdw_port_config *port_config,
- unsigned int num_config)
-{
- struct sdw_port_runtime *p_rt;
- int i, ret;
-
- /* Iterate for number of ports to perform initialization */
- for (i = 0; i < num_config; i++) {
- p_rt = sdw_port_alloc(&slave->dev, port_config, i);
- if (!p_rt)
- return -ENOMEM;
-
- /*
- * TODO: Check valid port range as defined by DisCo/
- * slave
- */
- ret = sdw_is_valid_port_range(&slave->dev, p_rt);
- if (ret < 0) {
- kfree(p_rt);
- return ret;
- }
-
- /*
- * TODO: Check port capabilities for requested
- * configuration (audio mode support)
- */
-
- list_add_tail(&p_rt->port_node, &s_rt->port_list);
- }
-
- return 0;
-}
-
-/**
- * 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 = NULL;
- 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", bus->link_id);
- ret = -EINVAL;
- goto unlock;
- }
-
- m_rt = sdw_alloc_master_rt(bus, stream_config, stream);
- if (!m_rt) {
- dev_err(bus->dev,
- "Master runtime config failed for stream:%s",
- stream->name);
- ret = -ENOMEM;
- goto unlock;
- }
-
- ret = sdw_config_stream(bus->dev, stream, stream_config, false);
- if (ret)
- goto stream_error;
-
- ret = sdw_master_port_config(bus, m_rt, port_config, num_ports);
- if (ret)
- goto stream_error;
-
- stream->m_rt_count++;
-
- goto unlock;
-
-stream_error:
- sdw_release_master_stream(m_rt, stream);
-unlock:
- mutex_unlock(&bus->bus_lock);
- return ret;
-}
-EXPORT_SYMBOL(sdw_stream_add_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;
- int ret;
-
- mutex_lock(&slave->bus->bus_lock);
-
- /*
- * 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_alloc_master_rt(slave->bus, stream_config, stream);
- if (!m_rt) {
- dev_err(&slave->dev,
- "alloc master runtime failed for stream:%s",
- stream->name);
- ret = -ENOMEM;
- goto error;
- }
-
- s_rt = sdw_alloc_slave_rt(slave, stream_config, stream);
- if (!s_rt) {
- dev_err(&slave->dev,
- "Slave runtime config failed for stream:%s",
- stream->name);
- ret = -ENOMEM;
- goto stream_error;
- }
-
- ret = sdw_config_stream(&slave->dev, stream, stream_config, true);
- if (ret)
- goto stream_error;
-
- list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list);
-
- ret = sdw_slave_port_config(slave, s_rt, port_config, num_ports);
- if (ret)
- goto stream_error;
-
- /*
- * 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 error;
-
-stream_error:
- /*
- * we hit error so cleanup the stream, release all Slave(s) and
- * Master runtime
- */
- sdw_release_master_stream(m_rt, stream);
-error:
- mutex_unlock(&slave->bus->bus_lock);
- return ret;
-}
-EXPORT_SYMBOL(sdw_stream_add_slave);
-
/**
* sdw_get_slave_dpn_prop() - Get Slave port capabilities
*
@@ -1385,13 +1378,18 @@ EXPORT_SYMBOL(sdw_stream_add_slave);
* @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)
+ enum sdw_data_direction direction,
+ unsigned int port_num)
{
struct sdw_dpn_prop *dpn_prop;
u8 num_ports;
int i;
+ if (!port_num) {
+ dev_err(&slave->dev, "%s: port_num is zero\n", __func__);
+ return NULL;
+ }
+
if (direction == SDW_DATA_DIR_TX) {
num_ports = hweight32(slave->prop.source_ports);
dpn_prop = slave->prop.src_dpn_prop;
@@ -1401,9 +1399,7 @@ struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
}
for (i = 0; i < num_ports; i++) {
- dpn_prop = &dpn_prop[i];
-
- if (dpn_prop->num == port_num)
+ if (dpn_prop[i].num == port_num)
return &dpn_prop[i];
}
@@ -1422,8 +1418,8 @@ struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
*/
static void sdw_acquire_bus_lock(struct sdw_stream_runtime *stream)
{
- struct sdw_master_runtime *m_rt = NULL;
- struct sdw_bus *bus = NULL;
+ 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) {
@@ -1444,8 +1440,8 @@ static void sdw_acquire_bus_lock(struct sdw_stream_runtime *stream)
*/
static void sdw_release_bus_lock(struct sdw_stream_runtime *stream)
{
- struct sdw_master_runtime *m_rt = NULL;
- struct sdw_bus *bus = NULL;
+ 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) {
@@ -1454,11 +1450,12 @@ static void sdw_release_bus_lock(struct sdw_stream_runtime *stream)
}
}
-static int _sdw_prepare_stream(struct sdw_stream_runtime *stream)
+static int _sdw_prepare_stream(struct sdw_stream_runtime *stream,
+ bool update_params)
{
- struct sdw_master_runtime *m_rt = NULL;
- struct sdw_bus *bus = NULL;
- struct sdw_master_prop *prop = NULL;
+ struct sdw_master_runtime *m_rt;
+ struct sdw_bus *bus;
+ struct sdw_master_prop *prop;
struct sdw_bus_params params;
int ret;
@@ -1469,28 +1466,39 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream)
memcpy(&params, &bus->params, sizeof(params));
/* TODO: Support Asynchronous mode */
- if ((prop->max_freq % stream->params.rate) != 0) {
- dev_err(bus->dev, "Async mode not supported");
+ if ((prop->max_clk_freq % stream->params.rate) != 0) {
+ dev_err(bus->dev, "Async mode not supported\n");
return -EINVAL;
}
- /* 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;
+ if (update_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, stream);
+ if (ret < 0) {
+ dev_err(bus->dev, "Compute params failed: %d\n",
+ ret);
+ goto restore_params;
+ }
+ }
+ }
/* Program params */
- ret = sdw_program_params(bus);
+ ret = sdw_program_params(bus, true);
if (ret < 0) {
- dev_err(bus->dev, "Program params failed: %d", ret);
+ dev_err(bus->dev, "Program params failed: %d\n", ret);
goto restore_params;
}
-
}
ret = do_bank_switch(stream);
if (ret < 0) {
- dev_err(bus->dev, "Bank switch failed: %d", ret);
+ pr_err("%s: do_bank_switch failed: %d\n", __func__, ret);
goto restore_params;
}
@@ -1500,9 +1508,9 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream)
/* 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",
- ret);
- return ret;
+ dev_err(bus->dev, "Prepare port(s) failed ret = %d\n",
+ ret);
+ goto restore_params;
}
}
@@ -1524,19 +1532,42 @@ restore_params:
*/
int sdw_prepare_stream(struct sdw_stream_runtime *stream)
{
- int ret = 0;
+ bool update_params = true;
+ int ret;
if (!stream) {
- pr_err("SoundWire: Handle not found for stream");
+ pr_err("SoundWire: Handle not found for stream\n");
return -EINVAL;
}
sdw_acquire_bus_lock(stream);
- ret = _sdw_prepare_stream(stream);
- if (ret < 0)
- pr_err("Prepare for stream:%s failed: %d", stream->name, ret);
+ 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;
}
@@ -1544,8 +1575,8 @@ EXPORT_SYMBOL(sdw_prepare_stream);
static int _sdw_enable_stream(struct sdw_stream_runtime *stream)
{
- struct sdw_master_runtime *m_rt = NULL;
- struct sdw_bus *bus = NULL;
+ struct sdw_master_runtime *m_rt;
+ struct sdw_bus *bus;
int ret;
/* Enable Master(s) and Slave(s) port(s) associated with stream */
@@ -1553,23 +1584,24 @@ static int _sdw_enable_stream(struct sdw_stream_runtime *stream)
bus = m_rt->bus;
/* Program params */
- ret = sdw_program_params(bus);
+ ret = sdw_program_params(bus, false);
if (ret < 0) {
- dev_err(bus->dev, "Program params failed: %d", ret);
+ dev_err(bus->dev, "%s: Program params failed: %d\n", __func__, 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", ret);
+ dev_err(bus->dev,
+ "Enable port(s) failed ret: %d\n", ret);
return ret;
}
}
ret = do_bank_switch(stream);
if (ret < 0) {
- dev_err(bus->dev, "Bank switch failed: %d", ret);
+ pr_err("%s: do_bank_switch failed: %d\n", __func__, ret);
return ret;
}
@@ -1586,19 +1618,31 @@ static int _sdw_enable_stream(struct sdw_stream_runtime *stream)
*/
int sdw_enable_stream(struct sdw_stream_runtime *stream)
{
- int ret = 0;
+ int ret;
if (!stream) {
- pr_err("SoundWire: Handle not found for 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);
- if (ret < 0)
- pr_err("Enable for stream:%s failed: %d", stream->name, ret);
+state_err:
sdw_release_bus_lock(stream);
return ret;
}
@@ -1606,32 +1650,51 @@ EXPORT_SYMBOL(sdw_enable_stream);
static int _sdw_disable_stream(struct sdw_stream_runtime *stream)
{
- struct sdw_master_runtime *m_rt = NULL;
- struct sdw_bus *bus = NULL;
+ struct sdw_master_runtime *m_rt;
int ret;
list_for_each_entry(m_rt, &stream->master_list, stream_node) {
- bus = m_rt->bus;
+ 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", ret);
+ 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) {
- bus = m_rt->bus;
+ struct sdw_bus *bus = m_rt->bus;
+
/* Program params */
- ret = sdw_program_params(bus);
+ ret = sdw_program_params(bus, false);
if (ret < 0) {
- dev_err(bus->dev, "Program params failed: %d", ret);
+ dev_err(bus->dev, "%s: Program params failed: %d\n", __func__, ret);
return ret;
}
}
- return do_bank_switch(stream);
+ ret = do_bank_switch(stream);
+ if (ret < 0) {
+ pr_err("%s: do_bank_switch failed: %d\n", __func__, 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;
}
/**
@@ -1643,19 +1706,30 @@ static int _sdw_disable_stream(struct sdw_stream_runtime *stream)
*/
int sdw_disable_stream(struct sdw_stream_runtime *stream)
{
- int ret = 0;
+ int ret;
if (!stream) {
- pr_err("SoundWire: Handle not found for 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);
- if (ret < 0)
- pr_err("Disable for stream:%s failed: %d", stream->name, ret);
+state_err:
sdw_release_bus_lock(stream);
return ret;
}
@@ -1663,33 +1737,68 @@ EXPORT_SYMBOL(sdw_disable_stream);
static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream)
{
- struct sdw_master_runtime *m_rt = NULL;
- struct sdw_bus *bus = NULL;
+ struct sdw_master_runtime *m_rt;
+ struct sdw_port_runtime *p_rt;
+ unsigned int multi_lane_bandwidth;
+ unsigned int bandwidth;
+ struct sdw_bus *bus;
+ int state = stream->state;
int ret = 0;
+ /*
+ * first mark the state as DEPREPARED so that it is not taken into account
+ * for bit allocation
+ */
+ stream->state = SDW_STREAM_DEPREPARED;
+
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", ret);
+ dev_err(bus->dev,
+ "De-prepare port(s) failed: %d\n", ret);
+ stream->state = state;
return ret;
}
+ multi_lane_bandwidth = 0;
+
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ if (!p_rt->lane)
+ continue;
+
+ bandwidth = m_rt->stream->params.rate * hweight32(p_rt->ch_mask) *
+ m_rt->stream->params.bps;
+ multi_lane_bandwidth += bandwidth;
+ bus->lane_used_bandwidth[p_rt->lane] -= bandwidth;
+ if (!bus->lane_used_bandwidth[p_rt->lane])
+ p_rt->lane = 0;
+ }
/* TODO: Update this during Device-Device support */
- bus->params.bandwidth -= m_rt->stream->params.rate *
- m_rt->ch_count * m_rt->stream->params.bps;
+ bandwidth = m_rt->stream->params.rate * m_rt->ch_count * m_rt->stream->params.bps;
+ bus->params.bandwidth -= bandwidth - multi_lane_bandwidth;
+
+ /* Compute params */
+ if (bus->compute_params) {
+ ret = bus->compute_params(bus, stream);
+ if (ret < 0) {
+ dev_err(bus->dev, "Compute params failed: %d\n",
+ ret);
+ stream->state = state;
+ return ret;
+ }
+ }
/* Program params */
- ret = sdw_program_params(bus);
+ ret = sdw_program_params(bus, false);
if (ret < 0) {
- dev_err(bus->dev, "Program params failed: %d", ret);
+ dev_err(bus->dev, "%s: Program params failed: %d\n", __func__, ret);
+ stream->state = state;
return ret;
}
-
}
- stream->state = SDW_STREAM_DEPREPARED;
return do_bank_switch(stream);
}
@@ -1702,19 +1811,430 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream)
*/
int sdw_deprepare_stream(struct sdw_stream_runtime *stream)
{
- int ret = 0;
+ int ret;
if (!stream) {
- pr_err("SoundWire: Handle not found for 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);
- if (ret < 0)
- pr_err("De-prepare for stream:%d failed: %d", ret, ret);
+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 = snd_soc_substream_to_rtd(substream);
+ 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
+ * @type: stream type (could be PCM ,PDM or BPT)
+ *
+ * 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, enum sdw_stream_type type)
+{
+ 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;
+ stream->type = type;
+
+ 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 = snd_soc_substream_to_rtd(substream);
+ 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, SDW_STREAM_PCM);
+ 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 = snd_soc_substream_to_rtd(substream);
+ struct sdw_stream_runtime *sdw_stream;
+ struct snd_soc_dai *dai;
+
+ /* Find stream from first CPU DAI */
+ dai = snd_soc_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,
+ const 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 = false;
+ 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) {
+ m_rt = sdw_master_rt_alloc(bus, stream);
+ if (IS_ERR(m_rt)) {
+ ret = PTR_ERR(m_rt);
+ dev_err(bus->dev, "%s: Master runtime alloc failed for stream:%s: %d\n",
+ __func__, stream->name, ret);
+ goto unlock;
+ }
+ if (!m_rt) {
+ dev_err(bus->dev, "%s: Master runtime alloc failed for stream:%s\n",
+ __func__, stream->name);
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ alloc_master_rt = true;
+ }
+
+ if (!sdw_master_port_allocated(m_rt)) {
+ ret = sdw_master_port_alloc(m_rt, num_ports);
+ if (ret)
+ goto alloc_error;
+
+ stream->m_rt_count++;
+ }
+
+ 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,
+ const 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 = false;
+ bool alloc_slave_rt = false;
+
+ 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) {
+ /*
+ * 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 (IS_ERR(m_rt)) {
+ ret = PTR_ERR(m_rt);
+ dev_err(&slave->dev, "%s: Master runtime alloc failed for stream:%s: %d\n",
+ __func__, stream->name, ret);
+ goto unlock;
+ }
+ if (!m_rt) {
+ dev_err(&slave->dev, "%s: Master runtime alloc failed for stream:%s\n",
+ __func__, stream->name);
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ alloc_master_rt = true;
+ }
+
+ s_rt = sdw_slave_rt_find(slave, stream);
+ if (!s_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);
+ ret = -ENOMEM;
+ goto alloc_error;
+ }
+
+ alloc_slave_rt = true;
+ }
+
+ if (!sdw_slave_port_allocated(s_rt)) {
+ ret = sdw_slave_port_alloc(slave, s_rt, num_ports);
+ if (ret)
+ goto alloc_error;
+ }
+
+ 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,
+ stream->type == SDW_STREAM_BPT);
+ 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 000000000000..fa048e112629
--- /dev/null
+++ b/drivers/soundwire/sysfs_local.h
@@ -0,0 +1,20 @@
+/* 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[];
+
+/* attributes for all soundwire devices */
+extern const struct attribute_group *sdw_attr_groups[];
+
+/* additional device-managed properties reported after driver probe */
+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 000000000000..c5c22d1708ec
--- /dev/null
+++ b/drivers/soundwire/sysfs_slave.c
@@ -0,0 +1,266 @@
+// 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,
+};
+
+static const struct attribute_group slave_attr_group = {
+ .attrs = slave_attrs,
+};
+
+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,
+};
+
+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,
+};
+
+static umode_t dp0_attr_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(kobj_to_dev(kobj));
+
+ if (slave->prop.dp0_prop)
+ return attr->mode;
+ return 0;
+}
+
+static bool dp0_group_visible(struct kobject *kobj)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(kobj_to_dev(kobj));
+
+ if (slave->prop.dp0_prop)
+ return true;
+ return false;
+}
+DEFINE_SYSFS_GROUP_VISIBLE(dp0);
+
+static const struct attribute_group dp0_group = {
+ .attrs = dp0_attrs,
+ .is_visible = SYSFS_GROUP_VISIBLE(dp0),
+ .name = "dp0",
+};
+
+const struct attribute_group *sdw_attr_groups[] = {
+ &slave_attr_group,
+ &sdw_slave_dev_attr_group,
+ &dp0_group,
+ NULL,
+};
+
+/*
+ * the status is shown in capital letters for UNATTACHED and RESERVED
+ * on purpose, to highlight 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 000000000000..a3fb380ee519
--- /dev/null
+++ b/drivers/soundwire/sysfs_slave_dpn.c
@@ -0,0 +1,304 @@
+// 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;
+
+ if (!slave->prop.source_ports && !slave->prop.sink_ports)
+ return 0;
+
+ 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;
+}