summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2023-07-05 10:54:43 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2023-07-05 10:54:43 -0700
commitfe1de55167963a1c0ebe1579e37a8a41495f0a81 (patch)
tree5fad0fa391dd217a332c62f075df88e4177581e2 /drivers
parent15ac468614e5e4fee82e1eb32568f427b0e51adc (diff)
parenta4857d1afdd1fa7ff763e1d07b1c2db521a5f9b1 (diff)
downloadlinux-fe1de55167963a1c0ebe1579e37a8a41495f0a81.tar.gz
linux-fe1de55167963a1c0ebe1579e37a8a41495f0a81.tar.bz2
linux-fe1de55167963a1c0ebe1579e37a8a41495f0a81.zip
Merge tag 'soundwire-6.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire
Pull soundwire updates from Vinod Koul: - Stream handling and slave alert handling - Qualcomm Soundwire v2.0.0 controller support - Intel ACE2.x initial support and code reorganization * tag 'soundwire-6.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire: (55 commits) soundwire: stream: Make master_list ordered to prevent deadlocks soundwire: bus: Prevent lockdep asserts when stream has multiple buses soundwire: qcom: fix storing port config out-of-bounds soundwire: intel_ace2x: fix SND_SOC_SOF_HDA_MLINK dependency soundwire: debugfs: Add missing SCP registers soundwire: stream: Remove unnecessary gotos soundwire: stream: Invert logic on runtime alloc flags soundwire: stream: Remove unneeded checks for NULL bus soundwire: bandwidth allocation: Remove pointless variable soundwire: cadence: revisit parity injection soundwire: intel/cadence: update hardware reset sequence soundwire: intel_bus_common: enable interrupts last soundwire: intel_bus_common: update error log soundwire: amd: Improve error message in remove callback soundwire: debugfs: fix unbalanced pm_runtime_put() soundwire: qcom: fix unbalanced pm_runtime_put() soundwire: qcom: set clk stop need reset flag at runtime soundwire: qcom: add software workaround for bus clash interrupt assertion soundwire: qcom: wait for fifo to be empty before suspend soundwire: qcom: drop unused struct qcom_swrm_ctrl members ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/soundwire/Kconfig1
-rw-r--r--drivers/soundwire/Makefile3
-rw-r--r--drivers/soundwire/amd_manager.c9
-rw-r--r--drivers/soundwire/bus.c30
-rw-r--r--drivers/soundwire/cadence_master.c50
-rw-r--r--drivers/soundwire/cadence_master.h5
-rw-r--r--drivers/soundwire/debugfs.c12
-rw-r--r--drivers/soundwire/generic_bandwidth_allocation.c10
-rw-r--r--drivers/soundwire/intel.c57
-rw-r--r--drivers/soundwire/intel.h16
-rw-r--r--drivers/soundwire/intel_ace2x.c393
-rw-r--r--drivers/soundwire/intel_ace2x_debugfs.c147
-rw-r--r--drivers/soundwire/intel_auxdevice.c17
-rw-r--r--drivers/soundwire/intel_bus_common.c92
-rw-r--r--drivers/soundwire/intel_init.c21
-rw-r--r--drivers/soundwire/qcom.c496
-rw-r--r--drivers/soundwire/stream.c161
17 files changed, 1136 insertions, 384 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig
index fa71c9a36df7..4d8f3b7024ae 100644
--- a/drivers/soundwire/Kconfig
+++ b/drivers/soundwire/Kconfig
@@ -37,6 +37,7 @@ config SOUNDWIRE_INTEL
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
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile
index 925566ff4272..c3d3ab3262d3 100644
--- a/drivers/soundwire/Makefile
+++ b/drivers/soundwire/Makefile
@@ -24,7 +24,8 @@ soundwire-cadence-y := cadence_master.o
obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o
#Intel driver
-soundwire-intel-y := intel.o intel_auxdevice.o intel_init.o dmi-quirks.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
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c
index 9fb7f91ca182..08aeb7ed00e1 100644
--- a/drivers/soundwire/amd_manager.c
+++ b/drivers/soundwire/amd_manager.c
@@ -972,15 +972,18 @@ static int amd_sdw_manager_probe(struct platform_device *pdev)
return 0;
}
-static int amd_sdw_manager_remove(struct platform_device *pdev)
+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->probe_work);
amd_disable_sdw_interrupts(amd_manager);
sdw_bus_master_delete(&amd_manager->bus);
- return amd_disable_sdw_manager(amd_manager);
+ 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)
@@ -1194,7 +1197,7 @@ static const struct dev_pm_ops amd_pm = {
static struct platform_driver amd_sdw_driver = {
.probe = &amd_sdw_manager_probe,
- .remove = &amd_sdw_manager_remove,
+ .remove_new = &amd_sdw_manager_remove,
.driver = {
.name = "amd_sdw_manager",
.pm = &amd_pm,
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c
index 1ea6a64f8c4a..dba920ec88f6 100644
--- a/drivers/soundwire/bus.c
+++ b/drivers/soundwire/bus.c
@@ -69,8 +69,17 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent,
return -EINVAL;
}
- mutex_init(&bus->msg_lock);
- mutex_init(&bus->bus_lock);
+ /*
+ * 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);
@@ -181,6 +190,8 @@ void sdw_bus_master_delete(struct sdw_bus *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_bus_master_delete);
@@ -769,6 +780,9 @@ static int sdw_assign_device_num(struct sdw_slave *slave)
/* After xfer of msg, restore dev_num */
slave->dev_num = slave->dev_num_sticky;
+ if (bus->ops && bus->ops->new_peripheral_assigned)
+ bus->ops->new_peripheral_assigned(bus, dev_num);
+
return 0;
}
@@ -1588,7 +1602,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
unsigned long port;
bool slave_notify;
u8 sdca_cascade = 0;
- u8 buf, buf2[2], _buf, _buf2[2];
+ u8 buf, buf2[2];
bool parity_check;
bool parity_quirk;
@@ -1745,9 +1759,9 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
"SDW_SCP_INT1 recheck read failed:%d\n", ret);
goto io_err;
}
- _buf = ret;
+ buf = ret;
- ret = sdw_nread_no_pm(slave, SDW_SCP_INTSTAT2, 2, _buf2);
+ ret = sdw_nread_no_pm(slave, SDW_SCP_INTSTAT2, 2, buf2);
if (ret < 0) {
dev_err(&slave->dev,
"SDW_SCP_INT2/3 recheck read failed:%d\n", ret);
@@ -1765,12 +1779,8 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
}
/*
- * Make sure no interrupts are pending, but filter to limit loop
- * to interrupts identified in the first status read
+ * Make sure no interrupts are pending
*/
- buf &= _buf;
- buf2[0] &= _buf2[0];
- buf2[1] &= _buf2[1];
stat = buf || buf2[0] || buf2[1] || sdca_cascade;
/*
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c
index 39502bc75712..0efc1c3bee5f 100644
--- a/drivers/soundwire/cadence_master.c
+++ b/drivers/soundwire/cadence_master.c
@@ -283,6 +283,29 @@ static int cdns_config_update(struct sdw_cdns *cdns)
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
*/
@@ -433,9 +456,9 @@ static int cdns_parity_error_injection(void *data, u64 value)
CDNS_IP_MCP_CMDCTRL_INSERT_PARITY_ERR);
/* commit changes */
- cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE,
- CDNS_MCP_CONFIG_UPDATE_BIT,
- CDNS_MCP_CONFIG_UPDATE_BIT);
+ 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);
@@ -447,16 +470,17 @@ static int cdns_parity_error_injection(void *data, u64 value)
0);
/* commit changes */
- cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE,
- CDNS_MCP_CONFIG_UPDATE_BIT,
- CDNS_MCP_CONFIG_UPDATE_BIT);
-
- /* Continue bus operation with parity error injection disabled */
- mutex_unlock(&bus->bus_lock);
+ 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.
@@ -1116,13 +1140,7 @@ int sdw_cdns_exit_reset(struct sdw_cdns *cdns)
CDNS_MCP_CONTROL_HW_RST);
/* commit changes */
- cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE,
- CDNS_MCP_CONFIG_UPDATE_BIT,
- CDNS_MCP_CONFIG_UPDATE_BIT);
-
- /* don't wait here */
- return 0;
-
+ return cdns_config_update(cdns);
}
EXPORT_SYMBOL(sdw_cdns_exit_reset);
diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h
index 27c56274217f..bc84435e420f 100644
--- a/drivers/soundwire/cadence_master.h
+++ b/drivers/soundwire/cadence_master.h
@@ -14,6 +14,8 @@
*/
#define CDNS_MCP_IP_MAX_CMD_LEN 32
+#define SDW_CADENCE_MCP_IP_OFFSET 0x4000
+
/**
* struct sdw_cdns_pdi: PDI (Physical Data Interface) instance
*
@@ -197,4 +199,7 @@ int cdns_set_sdw_stream(struct snd_soc_dai *dai,
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);
+
#endif /* __SDW_CADENCE_H */
diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c
index dea782e0edc4..d1553cb77187 100644
--- a/drivers/soundwire/debugfs.c
+++ b/drivers/soundwire/debugfs.c
@@ -56,8 +56,9 @@ static int sdw_slave_reg_show(struct seq_file *s_file, void *data)
if (!buf)
return -ENOMEM;
- ret = pm_runtime_resume_and_get(&slave->dev);
+ ret = pm_runtime_get_sync(&slave->dev);
if (ret < 0 && ret != -EACCES) {
+ pm_runtime_put_noidle(&slave->dev);
kfree(buf);
return ret;
}
@@ -85,10 +86,17 @@ static int sdw_slave_reg_show(struct seq_file *s_file, void *data)
/* SCP registers */
ret += scnprintf(buf + ret, RD_BUF - ret, "\nSCP\n");
- for (i = SDW_SCP_INT1; i <= SDW_SCP_BANKDELAY; i++)
+ 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_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
diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c
index 325c475b6a66..31162f2b5638 100644
--- a/drivers/soundwire/generic_bandwidth_allocation.c
+++ b/drivers/soundwire/generic_bandwidth_allocation.c
@@ -139,20 +139,16 @@ static void _sdw_compute_port_params(struct sdw_bus *bus,
{
struct sdw_master_runtime *m_rt;
int hstop = bus->params.col - 1;
- int block_offset, port_bo, i;
+ int port_bo, i;
/* Run loop for all groups to compute transport parameters */
for (i = 0; i < count; i++) {
port_bo = 1;
- block_offset = 1;
list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
- sdw_compute_master_ports(m_rt, &params[i],
- port_bo, hstop);
+ sdw_compute_master_ports(m_rt, &params[i], port_bo, hstop);
- block_offset += m_rt->ch_count *
- m_rt->stream->params.bps;
- port_bo = block_offset;
+ port_bo += m_rt->ch_count * m_rt->stream->params.bps;
}
hstop = hstop - params[i].hwidth;
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c
index 238acf5c97a9..26d8485427dd 100644
--- a/drivers/soundwire/intel.c
+++ b/drivers/soundwire/intel.c
@@ -260,7 +260,7 @@ static void intel_shim_init(struct sdw_intel *sdw)
{
void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
- u16 ioctl = 0, act = 0;
+ u16 ioctl = 0, act;
/* Initialize Shim */
ioctl |= SDW_SHIM_IOCTL_BKE;
@@ -281,6 +281,7 @@ static void intel_shim_init(struct sdw_intel *sdw)
intel_shim_glue_to_master_ip(sdw);
+ 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;
@@ -643,7 +644,7 @@ intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
}
static int intel_params_stream(struct sdw_intel *sdw,
- int stream,
+ struct snd_pcm_substream *substream,
struct snd_soc_dai *dai,
struct snd_pcm_hw_params *hw_params,
int link_id, int alh_stream_id)
@@ -651,7 +652,7 @@ static int intel_params_stream(struct sdw_intel *sdw,
struct sdw_intel_link_res *res = sdw->link_res;
struct sdw_intel_stream_params_data params_data;
- params_data.stream = stream; /* direction */
+ params_data.substream = substream;
params_data.dai = dai;
params_data.hw_params = hw_params;
params_data.link_id = link_id;
@@ -663,25 +664,6 @@ static int intel_params_stream(struct sdw_intel *sdw,
return -EIO;
}
-static int intel_free_stream(struct sdw_intel *sdw,
- int stream,
- struct snd_soc_dai *dai,
- int link_id)
-{
- struct sdw_intel_link_res *res = sdw->link_res;
- struct sdw_intel_stream_free_data free_data;
-
- free_data.stream = stream; /* direction */
- free_data.dai = dai;
- free_data.link_id = link_id;
-
- if (res->ops && res->ops->free_stream && res->dev)
- return res->ops->free_stream(res->dev,
- &free_data);
-
- return 0;
-}
-
/*
* DAI routines
*/
@@ -727,7 +709,7 @@ static int intel_hw_params(struct snd_pcm_substream *substream,
dai_runtime->pdi = pdi;
/* Inform DSP about PDI stream number */
- ret = intel_params_stream(sdw, substream->stream, dai, params,
+ ret = intel_params_stream(sdw, substream, dai, params,
sdw->instance,
pdi->intel_alh_id);
if (ret)
@@ -804,7 +786,7 @@ static int intel_prepare(struct snd_pcm_substream *substream,
sdw_cdns_config_stream(cdns, ch, dir, dai_runtime->pdi);
/* Inform DSP about PDI stream number */
- ret = intel_params_stream(sdw, substream->stream, dai,
+ ret = intel_params_stream(sdw, substream, dai,
hw_params,
sdw->instance,
dai_runtime->pdi->intel_alh_id);
@@ -817,7 +799,6 @@ 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;
@@ -838,12 +819,6 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
return ret;
}
- ret = intel_free_stream(sdw, substream->stream, dai, sdw->instance);
- if (ret < 0) {
- dev_err(dai->dev, "intel_free_stream: failed %d\n", ret);
- return ret;
- }
-
dai_runtime->pdi = NULL;
return 0;
@@ -871,19 +846,9 @@ static void *intel_get_sdw_stream(struct snd_soc_dai *dai,
static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
- struct sdw_intel *sdw = cdns_to_intel(cdns);
- struct sdw_intel_link_res *res = sdw->link_res;
struct sdw_cdns_dai_runtime *dai_runtime;
int ret = 0;
- /*
- * The .trigger callback is used to send required IPC to audio
- * firmware. The .free_stream callback will still be called
- * by intel_free_stream() in the TRIGGER_SUSPEND case.
- */
- if (res->ops && res->ops->trigger)
- res->ops->trigger(dai, cmd, substream->stream);
-
dai_runtime = cdns->dai_runtime_array[dai->id];
if (!dai_runtime) {
dev_err(dai->dev, "failed to get dai runtime in %s\n",
@@ -903,7 +868,6 @@ static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct sn
dai_runtime->suspended = true;
- ret = intel_free_stream(sdw, substream->stream, dai, sdw->instance);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
@@ -949,9 +913,7 @@ static int intel_component_dais_suspend(struct snd_soc_component *component)
*/
for_each_component_dais(component, dai) {
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
- struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_cdns_dai_runtime *dai_runtime;
- int ret;
dai_runtime = cdns->dai_runtime_array[dai->id];
@@ -961,13 +923,8 @@ static int intel_component_dais_suspend(struct snd_soc_component *component)
if (dai_runtime->suspended)
continue;
- if (dai_runtime->paused) {
+ if (dai_runtime->paused)
dai_runtime->suspended = true;
-
- ret = intel_free_stream(sdw, dai_runtime->direction, dai, sdw->instance);
- if (ret < 0)
- return ret;
- }
}
return 0;
diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h
index 09d479f2c77b..511932c55216 100644
--- a/drivers/soundwire/intel.h
+++ b/drivers/soundwire/intel.h
@@ -4,13 +4,17 @@
#ifndef __SDW_INTEL_LOCAL_H
#define __SDW_INTEL_LOCAL_H
+struct hdac_bus;
+
/**
* 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
@@ -21,13 +25,16 @@
* @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;
@@ -38,6 +45,7 @@ struct sdw_intel_link_res {
u32 link_mask;
struct sdw_cdns *cdns;
struct list_head list;
+ struct hdac_bus *hbus;
};
struct sdw_intel {
@@ -87,6 +95,14 @@ static inline void intel_writew(void __iomem *base, int offset, u16 value)
(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))
diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c
new file mode 100644
index 000000000000..1be0bea5f40f
--- /dev/null
+++ b/drivers/soundwire/intel_ace2x.c
@@ -0,0 +1,393 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+// Copyright(c) 2023 Intel Corporation. All rights reserved.
+
+/*
+ * Soundwire Intel ops for LunarLake
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
+#include <sound/hda-mlink.h>
+#include "cadence_master.h"
+#include "bus.h"
+#include "intel.h"
+
+/*
+ * shim vendor-specific (vs) ops
+ */
+
+static void intel_shim_vs_init(struct sdw_intel *sdw)
+{
+ void __iomem *shim_vs = sdw->link_res->shim_vs;
+ u16 act = 0;
+
+ u16p_replace_bits(&act, 0x1, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAIS);
+ act |= SDW_SHIM2_INTEL_VS_ACTMCTL_DACTQE;
+ act |= SDW_SHIM2_INTEL_VS_ACTMCTL_DODS;
+ intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL, act);
+ usleep_range(10, 15);
+}
+
+static int intel_shim_check_wake(struct sdw_intel *sdw)
+{
+ void __iomem *shim_vs;
+ u16 wake_sts;
+
+ shim_vs = sdw->link_res->shim_vs;
+ wake_sts = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS);
+
+ return wake_sts & SDW_SHIM2_INTEL_VS_WAKEEN_PWS;
+}
+
+static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
+{
+ void __iomem *shim_vs = sdw->link_res->shim_vs;
+ u16 wake_en;
+ u16 wake_sts;
+
+ wake_en = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN);
+
+ if (wake_enable) {
+ /* Enable the wakeup */
+ wake_en |= SDW_SHIM2_INTEL_VS_WAKEEN_PWE;
+ intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN, wake_en);
+ } else {
+ /* Disable the wake up interrupt */
+ wake_en &= ~SDW_SHIM2_INTEL_VS_WAKEEN_PWE;
+ intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN, wake_en);
+
+ /* Clear wake status (W1C) */
+ wake_sts = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS);
+ wake_sts |= SDW_SHIM2_INTEL_VS_WAKEEN_PWS;
+ intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS, wake_sts);
+ }
+}
+
+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 syncprd;
+ int ret;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ if (!*shim_mask) {
+ /* we first need to program the SyncPRD/CPU registers */
+ dev_dbg(sdw->cdns.dev, "first link up, programming SYNCPRD\n");
+
+ if (prop->mclk_freq % 6000000)
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4;
+ else
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24;
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ if (!*shim_mask) {
+ /* 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;
+ }
+ }
+
+ *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);
+
+ 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 operations
+ */
+static const struct snd_soc_dai_ops intel_pcm_dai_ops = {
+};
+
+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);