summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/linux/soundwire/sdw_registers.h2
-rw-r--r--include/sound/sdca.h14
-rw-r--r--include/sound/sdca_regmap.h2
-rw-r--r--sound/soc/sdca/Kconfig18
-rw-r--r--sound/soc/sdca/Makefile10
-rw-r--r--sound/soc/sdca/sdca_class.c304
-rw-r--r--sound/soc/sdca/sdca_class.h37
-rw-r--r--sound/soc/sdca/sdca_class_function.c460
-rw-r--r--sound/soc/sdca/sdca_fdl.c93
-rw-r--r--sound/soc/sdca/sdca_function_device.c117
-rw-r--r--sound/soc/sdca/sdca_function_device.h15
-rw-r--r--sound/soc/sdca/sdca_functions.c52
-rw-r--r--sound/soc/sdca/sdca_hid.c3
-rw-r--r--sound/soc/sdca/sdca_interrupts.c39
-rw-r--r--sound/soc/sdca/sdca_regmap.c89
15 files changed, 1132 insertions, 123 deletions
diff --git a/include/linux/soundwire/sdw_registers.h b/include/linux/soundwire/sdw_registers.h
index 0a5939285583..cae8a0a5a9b0 100644
--- a/include/linux/soundwire/sdw_registers.h
+++ b/include/linux/soundwire/sdw_registers.h
@@ -355,4 +355,6 @@
/* Check the reserved and fixed bits in address */
#define SDW_SDCA_VALID_CTL(reg) (((reg) & (GENMASK(31, 25) | BIT(18) | BIT(13))) == BIT(30))
+#define SDW_SDCA_MAX_REGISTER 0x47FFFFFF
+
#endif /* __SDW_REGISTERS_H */
diff --git a/include/sound/sdca.h b/include/sound/sdca.h
index d38cdbfeb35f..67ff3c88705d 100644
--- a/include/sound/sdca.h
+++ b/include/sound/sdca.h
@@ -13,19 +13,23 @@
#include <linux/kconfig.h>
struct acpi_table_swft;
+struct fwnode_handle;
struct sdw_slave;
+struct sdca_dev;
#define SDCA_MAX_FUNCTION_COUNT 8
/**
* struct sdca_function_desc - short descriptor for an SDCA Function
* @node: firmware node for the Function.
+ * @func_dev: pointer to SDCA function device.
* @name: Human-readable string.
* @type: Function topology type.
* @adr: ACPI address (used for SDCA register access).
*/
struct sdca_function_desc {
struct fwnode_handle *node;
+ struct sdca_dev *func_dev;
const char *name;
u32 type;
u8 adr;
@@ -58,6 +62,8 @@ void sdca_lookup_functions(struct sdw_slave *slave);
void sdca_lookup_swft(struct sdw_slave *slave);
void sdca_lookup_interface_revision(struct sdw_slave *slave);
bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk);
+int sdca_dev_register_functions(struct sdw_slave *slave);
+void sdca_dev_unregister_functions(struct sdw_slave *slave);
#else
@@ -68,6 +74,14 @@ static inline bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_qu
{
return false;
}
+
+static inline int sdca_dev_register_functions(struct sdw_slave *slave)
+{
+ return 0;
+}
+
+static inline void sdca_dev_unregister_functions(struct sdw_slave *slave) {}
+
#endif
#endif
diff --git a/include/sound/sdca_regmap.h b/include/sound/sdca_regmap.h
index b2e3c2ad2bb8..792540a530fc 100644
--- a/include/sound/sdca_regmap.h
+++ b/include/sound/sdca_regmap.h
@@ -27,5 +27,7 @@ int sdca_regmap_populate_constants(struct device *dev, struct sdca_function_data
int sdca_regmap_write_defaults(struct device *dev, struct regmap *regmap,
struct sdca_function_data *function);
+int sdca_regmap_write_init(struct device *dev, struct regmap *regmap,
+ struct sdca_function_data *function);
#endif // __SDCA_REGMAP_H__
diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig
index a73920d07073..3c6bf0e2c2f0 100644
--- a/sound/soc/sdca/Kconfig
+++ b/sound/soc/sdca/Kconfig
@@ -4,6 +4,7 @@ menu "SoundWire (SDCA)"
config SND_SOC_SDCA
tristate
depends on ACPI
+ select AUXILIARY_BUS
help
This option enables support for the MIPI SoundWire Device
Class for Audio (SDCA).
@@ -36,4 +37,21 @@ config SND_SOC_SDCA_FDL
config SND_SOC_SDCA_OPTIONAL
def_tristate SND_SOC_SDCA || !SND_SOC_SDCA
+config SND_SOC_SDCA_CLASS
+ tristate "SDCA Class Driver"
+ depends on SND_SOC_SDCA
+ select SND_SOC_SDCA_CLASS_FUNCTION
+ select SND_SOC_SDCA_FDL
+ select SND_SOC_SDCA_HID
+ select SND_SOC_SDCA_IRQ
+ help
+ This option enables support for the SDCA Class driver which should
+ support any class compliant SDCA part.
+
+config SND_SOC_SDCA_CLASS_FUNCTION
+ tristate
+ help
+ This option enables support for the SDCA Class Function drivers,
+ these implement the individual functions of the SDCA Class driver.
+
endmenu
diff --git a/sound/soc/sdca/Makefile b/sound/soc/sdca/Makefile
index be911c399bbd..f6b73275d964 100644
--- a/sound/soc/sdca/Makefile
+++ b/sound/soc/sdca/Makefile
@@ -1,9 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-soc-sdca-y := sdca_functions.o sdca_device.o sdca_regmap.o sdca_asoc.o \
- sdca_ump.o
+snd-soc-sdca-y := sdca_functions.o sdca_device.o sdca_function_device.o \
+ sdca_regmap.o sdca_asoc.o sdca_ump.o
snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_HID) += sdca_hid.o
snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_IRQ) += sdca_interrupts.o
snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_FDL) += sdca_fdl.o
+snd-soc-sdca-class-y := sdca_class.o
+snd-soc-sdca-class-function-y := sdca_class_function.o
+
obj-$(CONFIG_SND_SOC_SDCA) += snd-soc-sdca.o
+
+obj-$(CONFIG_SND_SOC_SDCA_CLASS) += snd-soc-sdca-class.o
+obj-$(CONFIG_SND_SOC_SDCA_CLASS_FUNCTION) += snd-soc-sdca-class-function.o
diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c
new file mode 100644
index 000000000000..349d32933ba8
--- /dev/null
+++ b/sound/soc/sdca/sdca_class.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+/*
+ * The MIPI SDCA specification is available for public downloads at
+ * https://www.mipi.org/mipi-sdca-v1-0-download
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/sdca.h>
+#include <sound/sdca_function.h>
+#include <sound/sdca_interrupts.h>
+#include <sound/sdca_regmap.h>
+#include "sdca_class.h"
+
+#define CLASS_SDW_ATTACH_TIMEOUT_MS 5000
+
+static int class_read_prop(struct sdw_slave *sdw)
+{
+ struct sdw_slave_prop *prop = &sdw->prop;
+
+ sdw_slave_read_prop(sdw);
+
+ prop->use_domain_irq = true;
+ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY |
+ SDW_SCP_INT1_IMPL_DEF;
+
+ return 0;
+}
+
+static int class_sdw_update_status(struct sdw_slave *sdw, enum sdw_slave_status status)
+{
+ struct sdca_class_drv *drv = dev_get_drvdata(&sdw->dev);
+
+ switch (status) {
+ case SDW_SLAVE_ATTACHED:
+ dev_dbg(drv->dev, "device attach\n");
+
+ drv->attached = true;
+
+ complete(&drv->device_attach);
+ break;
+ case SDW_SLAVE_UNATTACHED:
+ dev_dbg(drv->dev, "device detach\n");
+
+ drv->attached = false;
+
+ reinit_completion(&drv->device_attach);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct sdw_slave_ops class_sdw_ops = {
+ .read_prop = class_read_prop,
+ .update_status = class_sdw_update_status,
+};
+
+static void class_regmap_lock(void *data)
+{
+ struct mutex *lock = data;
+
+ mutex_lock(lock);
+}
+
+static void class_regmap_unlock(void *data)
+{
+ struct mutex *lock = data;
+
+ mutex_unlock(lock);
+}
+
+static int class_wait_for_attach(struct sdca_class_drv *drv)
+{
+ if (!drv->attached) {
+ unsigned long timeout = msecs_to_jiffies(CLASS_SDW_ATTACH_TIMEOUT_MS);
+ unsigned long time;
+
+ time = wait_for_completion_timeout(&drv->device_attach, timeout);
+ if (!time) {
+ dev_err(drv->dev, "timed out waiting for device re-attach\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ regcache_cache_only(drv->dev_regmap, false);
+
+ return 0;
+}
+
+static bool class_dev_regmap_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SDW_SCP_SDCA_INTMASK1 ... SDW_SCP_SDCA_INTMASK4:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool class_dev_regmap_precious(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INT4:
+ case SDW_SCP_SDCA_INTMASK1 ... SDW_SCP_SDCA_INTMASK4:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config class_dev_regmap_config = {
+ .name = "sdca-device",
+ .reg_bits = 32,
+ .val_bits = 8,
+
+ .max_register = SDW_SDCA_MAX_REGISTER,
+ .volatile_reg = class_dev_regmap_volatile,
+ .precious_reg = class_dev_regmap_precious,
+
+ .cache_type = REGCACHE_MAPLE,
+
+ .lock = class_regmap_lock,
+ .unlock = class_regmap_unlock,
+};
+
+static void class_boot_work(struct work_struct *work)
+{
+ struct sdca_class_drv *drv = container_of(work,
+ struct sdca_class_drv,
+ boot_work);
+ int ret;
+
+ ret = class_wait_for_attach(drv);
+ if (ret)
+ goto err;
+
+ drv->irq_info = sdca_irq_allocate(drv->dev, drv->dev_regmap,
+ drv->sdw->irq);
+ if (IS_ERR(drv->irq_info))
+ goto err;
+
+ ret = sdca_dev_register_functions(drv->sdw);
+ if (ret)
+ goto err;
+
+ dev_dbg(drv->dev, "boot work complete\n");
+
+ pm_runtime_mark_last_busy(drv->dev);
+ pm_runtime_put_autosuspend(drv->dev);
+
+ return;
+
+err:
+ pm_runtime_put_sync(drv->dev);
+}
+
+static void class_dev_remove(void *data)
+{
+ struct sdca_class_drv *drv = data;
+
+ cancel_work_sync(&drv->boot_work);
+
+ sdca_dev_unregister_functions(drv->sdw);
+}
+
+static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id)
+{
+ struct device *dev = &sdw->dev;
+ struct sdca_device_data *data = &sdw->sdca_data;
+ struct regmap_config *dev_config;
+ struct sdca_class_drv *drv;
+ int ret;
+
+ sdca_lookup_swft(sdw);
+
+ drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ dev_config = devm_kmemdup(dev, &class_dev_regmap_config,
+ sizeof(*dev_config), GFP_KERNEL);
+ if (!dev_config)
+ return -ENOMEM;
+
+ drv->functions = devm_kcalloc(dev, data->num_functions,
+ sizeof(*drv->functions),
+ GFP_KERNEL);
+ if (!drv->functions)
+ return -ENOMEM;
+
+ drv->dev = dev;
+ drv->sdw = sdw;
+ mutex_init(&drv->regmap_lock);
+
+ dev_set_drvdata(drv->dev, drv);
+
+ INIT_WORK(&drv->boot_work, class_boot_work);
+ init_completion(&drv->device_attach);
+
+ dev_config->lock_arg = &drv->regmap_lock;
+
+ drv->dev_regmap = devm_regmap_init_sdw(sdw, dev_config);
+ if (IS_ERR(drv->dev_regmap))
+ return dev_err_probe(drv->dev, PTR_ERR(drv->dev_regmap),
+ "failed to create device regmap\n");
+
+ regcache_cache_only(drv->dev_regmap, true);
+
+ pm_runtime_set_autosuspend_delay(dev, 250);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_get_noresume(dev);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, class_dev_remove, drv);
+ if (ret)
+ return ret;
+
+ queue_work(system_long_wq, &drv->boot_work);
+
+ return 0;
+}
+
+static int class_runtime_suspend(struct device *dev)
+{
+ struct sdca_class_drv *drv = dev_get_drvdata(dev);
+
+ /*
+ * Whilst the driver doesn't power the chip down here, going into runtime
+ * suspend lets the SoundWire bus power down, which means the driver
+ * can't communicate with the device any more.
+ */
+ regcache_cache_only(drv->dev_regmap, true);
+
+ return 0;
+}
+
+static int class_runtime_resume(struct device *dev)
+{
+ struct sdca_class_drv *drv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = class_wait_for_attach(drv);
+ if (ret)
+ goto err;
+
+ regcache_mark_dirty(drv->dev_regmap);
+
+ ret = regcache_sync(drv->dev_regmap);
+ if (ret) {
+ dev_err(drv->dev, "failed to restore cache: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ regcache_cache_only(drv->dev_regmap, true);
+
+ return ret;
+}
+
+static const struct dev_pm_ops class_pm_ops = {
+ RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL)
+};
+
+static const struct sdw_device_id class_sdw_id[] = {
+ SDW_SLAVE_ENTRY(0x01FA, 0x4245, 0),
+ {}
+};
+MODULE_DEVICE_TABLE(sdw, class_sdw_id);
+
+static struct sdw_driver class_sdw_driver = {
+ .driver = {
+ .name = "sdca_class",
+ .pm = pm_ptr(&class_pm_ops),
+ },
+
+ .probe = class_sdw_probe,
+ .id_table = class_sdw_id,
+ .ops = &class_sdw_ops,
+};
+module_sdw_driver(class_sdw_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SDCA Class Driver");
+MODULE_IMPORT_NS("SND_SOC_SDCA");
diff --git a/sound/soc/sdca/sdca_class.h b/sound/soc/sdca/sdca_class.h
new file mode 100644
index 000000000000..bb4c9dd12429
--- /dev/null
+++ b/sound/soc/sdca/sdca_class.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * The MIPI SDCA specification is available for public downloads at
+ * https://www.mipi.org/mipi-sdca-v1-0-download
+ *
+ * Copyright (C) 2025 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef __SDCA_CLASS_H__
+#define __SDCA_CLASS_H__
+
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+struct device;
+struct regmap;
+struct sdw_slave;
+struct sdca_function_data;
+
+struct sdca_class_drv {
+ struct device *dev;
+ struct regmap *dev_regmap;
+ struct sdw_slave *sdw;
+
+ struct sdca_function_data *functions;
+ struct sdca_interrupt_info *irq_info;
+
+ struct mutex regmap_lock;
+ struct work_struct boot_work;
+ struct completion device_attach;
+
+ bool attached;
+};
+
+#endif /* __SDCA_CLASS_H__ */
diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
new file mode 100644
index 000000000000..0028482a1e75
--- /dev/null
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+/*
+ * The MIPI SDCA specification is available for public downloads at
+ * https://www.mipi.org/mipi-sdca-v1-0-download
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <sound/pcm.h>
+#include <sound/sdca_asoc.h>
+#include <sound/sdca_fdl.h>
+#include <sound/sdca_function.h>
+#include <sound/sdca_interrupts.h>
+#include <sound/sdca_regmap.h>
+#include <sound/sdw.h>
+#include <sound/soc-component.h>
+#include <sound/soc-dai.h>
+#include <sound/soc.h>
+#include "sdca_class.h"
+
+struct class_function_drv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct sdca_class_drv *core;
+
+ struct sdca_function_data *function;
+};
+
+static void class_function_regmap_lock(void *data)
+{
+ struct mutex *lock = data;
+
+ mutex_lock(lock);
+}
+
+static void class_function_regmap_unlock(void *data)
+{
+ struct mutex *lock = data;
+
+ mutex_unlock(lock);
+}
+
+static bool class_function_regmap_writeable(struct device *dev, unsigned int reg)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+ return sdca_regmap_writeable(drv->function, reg);
+}
+
+static bool class_function_regmap_readable(struct device *dev, unsigned int reg)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+ return sdca_regmap_readable(drv->function, reg);
+}
+
+static bool class_function_regmap_volatile(struct device *dev, unsigned int reg)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+ return sdca_regmap_volatile(drv->function, reg);
+}
+
+static const struct regmap_config class_function_regmap_config = {
+ .name = "sdca",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_format_endian = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+
+ .max_register = SDW_SDCA_MAX_REGISTER,
+ .readable_reg = class_function_regmap_readable,
+ .writeable_reg = class_function_regmap_writeable,
+ .volatile_reg = class_function_regmap_volatile,
+
+ .cache_type = REGCACHE_MAPLE,
+
+ .lock = class_function_regmap_lock,
+ .unlock = class_function_regmap_unlock,
+};
+
+static int class_function_regmap_mbq_size(struct device *dev, unsigned int reg)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+ return sdca_regmap_mbq_size(drv->function, reg);
+}
+
+static bool class_function_regmap_deferrable(struct device *dev, unsigned int reg)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+ return sdca_regmap_deferrable(drv->function, reg);
+}
+
+static const struct regmap_sdw_mbq_cfg class_function_mbq_config = {
+ .mbq_size = class_function_regmap_mbq_size,
+ .deferrable = class_function_regmap_deferrable,
+ .retry_us = 1000,
+ .timeout_us = 10000,
+};
+
+static int class_function_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);
+
+ return sdca_asoc_set_constraints(drv->dev, drv->regmap, drv->function,
+ substream, dai);
+}
+
+static int class_function_sdw_add_peripheral(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);
+ struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+ struct sdw_slave *sdw = dev_to_sdw_dev(drv->dev->parent);
+ struct sdw_stream_config sconfig = {0};
+ struct sdw_port_config pconfig = {0};
+ int ret;
+
+ if (!sdw_stream)
+ return -EINVAL;
+
+ snd_sdw_params_to_config(substream, params, &sconfig, &pconfig);
+
+ /*
+ * FIXME: As also noted in sdca_asoc_get_port(), currently only
+ * a single unshared port is supported for each DAI.
+ */
+ ret = sdca_asoc_get_port(drv->dev, drv->regmap, drv->function, dai);
+ if (ret < 0)
+ return ret;
+
+ pconfig.num = ret;
+
+ ret = sdw_stream_add_slave(sdw, &sconfig, &pconfig, 1, sdw_stream);
+ if (ret) {
+ dev_err(drv->dev, "failed to add sdw stream: %d\n", ret);
+ return ret;
+ }
+
+ return sdca_asoc_hw_params(drv->dev, drv->regmap, drv->function,
+ substream, params, dai);
+}
+
+static int class_function_sdw_remove_peripheral(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);
+ struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+ struct sdw_slave *sdw = dev_to_sdw_dev(drv->dev->parent);
+
+ if (!sdw_stream)
+ return -EINVAL;
+
+ return sdw_stream_remove_slave(sdw, sdw_stream);
+}
+
+static int class_function_sdw_set_stream(struct snd_soc_dai *dai, void *sdw_stream,
+ int direction)
+{
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops class_function_sdw_ops = {
+ .startup = class_function_startup,
+ .shutdown = sdca_asoc_free_constraints,
+ .set_stream = class_function_sdw_set_stream,
+ .hw_params = class_function_sdw_add_peripheral,
+ .hw_free = class_function_sdw_remove_peripheral,
+};
+
+static int class_function_component_probe(struct snd_soc_component *component)
+{
+ struct class_function_drv *drv = snd_soc_component_get_drvdata(component);
+ struct sdca_class_drv *core = drv->core;
+
+ return sdca_irq_populate(drv->function, component, core->irq_info);
+}
+
+static const struct snd_soc_component_driver class_function_component_drv = {
+ .probe = class_function_component_probe,
+ .endianness = 1,
+};
+
+static int class_function_boot(struct class_function_drv *drv)
+{
+ unsigned int reg = SDW_SDCA_CTL(drv->function->desc->adr,
+ SDCA_ENTITY_TYPE_ENTITY_0,
+ SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(drv->regmap, reg, &val);
+ if (ret < 0) {
+ dev_err(drv->dev, "failed to read function status: %d\n", ret);
+ return ret;
+ }
+
+ if (!(val & SDCA_CTL_ENTITY_0_FUNCTION_HAS_BEEN_RESET)) {
+ dev_dbg(drv->dev, "reset function device\n");
+
+ ret = sdca_reset_function(drv->dev, drv->function, drv->regmap);
+ if (ret)
+ return ret;
+ }
+
+ if (val & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) {
+ dev_dbg(drv->dev, "write initialisation\n");
+
+ ret = sdca_regmap_write_init(drv->dev, drv->core->dev_regmap,
+ drv->function);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(drv->regmap, reg,
+ SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION);
+ if (ret < 0) {
+ dev_err(drv->dev,
+ "failed to clear function init status: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /* Start FDL process */
+ ret = sdca_irq_populate_early(drv->dev, drv->regmap, drv->function,
+ drv->core->irq_info);
+ if (ret)
+ return ret;
+
+ ret = sdca_fdl_sync(drv->dev, drv->function, drv->core->irq_info);
+ if (ret)
+ return ret;
+
+ ret = sdca_regmap_write_defaults(drv->dev, drv->regmap, drv->function);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(drv->regmap, reg, 0xFF);
+ if (ret < 0) {
+ dev_err(drv->dev, "failed to clear function status: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int class_function_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *aux_dev_id)
+{
+ struct device *dev = &auxdev->dev;
+ struct sdca_class_drv *core = dev_get_drvdata(dev->parent);
+ struct sdca_device_data *data = &core->sdw->sdca_data;
+ struct sdca_function_desc *desc;
+ struct snd_soc_component_driver *cmp_drv;
+ struct snd_soc_dai_driver *dais;
+ struct class_function_drv *drv;
+ struct regmap_sdw_mbq_cfg *mbq_config;
+ struct regmap_config *config;
+ struct reg_default *defaults;
+ int ndefaults;
+ int num_dais;
+ int ret;
+ int i;
+
+ drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ cmp_drv = devm_kmemdup(dev, &class_function_component_drv, sizeof(*cmp_drv),
+ GFP_KERNEL);
+ if (!cmp_drv)
+ return -ENOMEM;
+
+ config = devm_kmemdup(dev, &class_function_regmap_config, sizeof(*config),
+ GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+
+ mbq_config = devm_kmemdup(dev, &class_function_mbq_config, sizeof(*mbq_config),
+ GFP_KERNEL);
+ if (!mbq_config)
+ return -ENOMEM;
+
+ drv->dev = dev;
+ drv->core = core;
+
+ for (i = 0; i < data->num_functions; i++) {
+ desc = &data->function[i];
+
+ if (desc->type == aux_dev_id->driver_data)
+ break;
+ }
+ if (i == core->sdw->sdca_data.num_functions) {
+ dev_err(dev, "failed to locate function\n");
+ return -EINVAL;
+ }
+
+ drv->function = &core->functions[i];
+
+ ret = sdca_parse_function(dev, core->sdw, desc, drv->function);
+ if (ret)
+ return ret;
+
+ ndefaults = sdca_regmap_count_constants(dev, drv->function);
+ if (ndefaults < 0)
+ return ndefaults;
+
+ defaults = devm_kcalloc(dev, ndefaults, sizeof(*defaults), GFP_KERNEL);
+ if (!defaults)
+ return -ENOMEM;
+
+ ret = sdca_regmap_populate_constants(dev, drv->function, defaults);
+ if (ret < 0)
+ return ret;
+
+ regcache_sort_defaults(defaults, ndefaults);
+
+ auxiliary_set_drvdata(auxdev, drv);
+
+ config->reg_defaults = defaults;
+ config->num_reg_defaults = ndefaults;
+ config->lock_arg = &core->regmap_lock;
+
+ if (drv->function->busy_max_delay) {
+ mbq_config->timeout_us = drv->function->busy_max_delay;
+ mbq_config->retry_us = umax(drv->function->busy_max_delay / 10,
+ mbq_config->retry_us);
+ }
+
+ drv->regmap = devm_regmap_init_sdw_mbq_cfg(dev, core->sdw, config, mbq_config);
+ if (IS_ERR(drv->regmap))
+ return dev_err_probe(dev, PTR_ERR(drv->regmap),
+ "failed to create regmap");
+
+ ret = sdca_asoc_populate_component(dev, drv->function, cmp_drv,
+ &dais, &num_dais,
+ &class_function_sdw_ops);
+ if (ret)
+ return ret;
+
+ pm_runtime_set_autosuspend_delay(dev, 200);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_get_noresume(dev);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = class_function_boot(drv);
+ if (ret)
+ return ret;
+
+ ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register component\n");
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static int class_function_runtime_suspend(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+ /*
+ * Whilst the driver doesn't power the chip down here, going into
+ * runtime suspend means the driver can't be sure the bus won't
+ * power down which would prevent communication with the device.
+ */
+ regcache_cache_only(drv->regmap, true);
+
+ return 0;
+}
+
+static int class_function_runtime_resume(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+ int ret;
+
+ regcache_mark_dirty(drv->regmap);
+ regcache_cache_only(drv->regmap, false);
+
+ ret = regcache_sync(drv->regmap);
+ if (ret) {
+ dev_err(drv->dev, "failed to restore register cache: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ regcache_cache_only(drv->regmap, true);
+
+ return ret;
+}
+
+static const struct dev_pm_ops class_function_pm_ops = {
+ RUNTIME_PM_OPS(class_function_runtime_suspend,
+ class_function_runtime_resume, NULL)
+};
+
+static const struct auxiliary_device_id class_function_id_table[] = {
+ {
+ .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SMART_AMP_NAME,
+ .driver_data = SDCA_FUNCTION_TYPE_SMART_AMP,
+ },
+ {
+ .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SMART_MIC_NAME,
+ .driver_data = SDCA_FUNCTION_TYPE_SMART_MIC,
+ },
+ {
+ .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_UAJ_NAME,
+ .driver_data = SDCA_FUNCTION_TYPE_UAJ,
+ },
+ {
+ .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_HID_NAME,
+ .driver_data = SDCA_FUNCTION_TYPE_HID,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, class_function_id_table);
+
+static struct auxiliary_driver class_function_drv = {
+ .driver = {
+ .name = "sdca_function",
+ .pm = pm_ptr(&class_function_pm_ops),
+ },
+
+ .probe = class_function_probe,
+ .id_table = class_function_id_table
+};
+module_auxiliary_driver(class_function_drv);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SDCA Class Function Driver");
+MODULE_IMPORT_NS("SND_SOC_SDCA");
diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c
index cb79dc3131b8..3180ebd07c40 100644
--- a/sound/soc/sdca/sdca_fdl.c
+++ b/sound/soc/sdca/sdca_fdl.c
@@ -51,6 +51,10 @@ int sdca_reset_function(struct device *dev, struct sdca_function_data *function,
return -EINVAL;
}
+ /*
+ * Poll up to 16 times but no more than once per ms, these are just
+ * arbitrarily selected values, so may be fine tuned in future.
+ */
poll_us = umin(function->reset_max_delay >> 4, 1000);
ret = regmap_read_poll_timeout(regmap, reg, val, !val, poll_us,
@@ -402,8 +406,6 @@ int sdca_fdl_process(struct sdca_interrupt *interrupt)
unsigned int reg, status;
int response, ret;
- guard(mutex)(&fdl_state->lock);
-
ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap,
interrupt->function, interrupt->entity,
interrupt->control);
@@ -412,56 +414,59 @@ int sdca_fdl_process(struct sdca_interrupt *interrupt)
sdca_ump_cancel_timeout(&fdl_state->timeout);
- reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id,
- SDCA_CTL_XU_FDL_STATUS, 0);
- ret = regmap_read(interrupt->function_regmap, reg, &status);
- if (ret < 0) {
- dev_err(dev, "failed to read FDL status: %d\n", ret);
- return ret;
- }
-
- dev_dbg(dev, "FDL status: %#x\n", status);
+ scoped_guard(mutex, &fdl_state->lock) {
+ reg = SDW_SDCA_CTL(interrupt->function->desc->adr,
+ interrupt->entity->id, SDCA_CTL_XU_FDL_STATUS, 0);
+ ret = regmap_read(interrupt->function_regmap, reg, &status);
+ if (ret < 0) {
+ dev_err(dev, "failed to read FDL status: %d\n", ret);
+ return ret;
+ }
- ret = fdl_status_process(interrupt, status);
- if (ret < 0)
- goto reset_function;
+ dev_dbg(dev, "FDL status: %#x\n", status);
- response = ret;
+ ret = fdl_status_process(interrupt, status);
+ if (ret < 0)
+ goto reset_function;
- dev_dbg(dev, "FDL response: %#x\n", response);
+ response = ret;
- ret = regmap_write(interrupt->function_regmap, reg,
- response | (status & ~SDCA_CTL_XU_FDLH_MASK));
- if (ret < 0) {
- dev_err(dev, "failed to set FDL status signal: %d\n", ret);
- return ret;
- }
+ dev_dbg(dev, "FDL response: %#x\n", response);
- ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap,
- interrupt->function, interrupt->entity,
- interrupt->control);
- if (ret)
- return ret;
-
- switch (response) {
- case SDCA_CTL_XU_FDLH_RESET_ACK:
- dev_dbg(dev, "FDL request reset\n");
+ ret = regmap_write(interrupt->function_regmap, reg,
+ response | (status & ~SDCA_CTL_XU_FDLH_MASK));
+ if (ret < 0) {
+ dev_err(dev, "failed to set FDL status signal: %d\n", ret);
+ return ret;
+ }
- switch (xu->reset_mechanism) {
- default:
- dev_warn(dev, "Requested reset mechanism not implemented\n");
+ ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap,
+ interrupt->function,
+ interrupt->entity,
+ interrupt->control);
+ if (ret)
+ return ret;
+
+ switch (response) {
+ case SDCA_CTL_XU_FDLH_RESET_ACK:
+ dev_dbg(dev, "FDL request reset\n");
+
+ switch (xu->reset_mechanism) {
+ default:
+ dev_warn(dev, "Requested reset mechanism not implemented\n");
+ fallthrough;
+ case SDCA_XU_RESET_FUNCTION:
+ goto reset_function;
+ }
+ case SDCA_CTL_XU_FDLH_COMPLETE:
+ if (status & SDCA_CTL_XU_FDLD_REQ_ABORT ||
+ status == SDCA_CTL_XU_FDLD_COMPLETE)
+ return 0;
fallthrough;
- case SDCA_XU_RESET_FUNCTION:
- goto reset_function;
- }
- case SDCA_CTL_XU_FDLH_COMPLETE:
- if (status & SDCA_CTL_XU_FDLD_REQ_ABORT ||
- status == SDCA_CTL_XU_FDLD_COMPLETE)
+ default:
+ sdca_ump_schedule_timeout(&fdl_state->timeout, xu->max_delay);
return 0;
- fallthrough;
- default:
- sdca_ump_schedule_timeout(&fdl_state->timeout, xu->max_delay);
- return 0;
+ }
}
reset_function:
diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c
new file mode 100644
index 000000000000..91c49d7389db
--- /dev/null
+++ b/sound/soc/sdca/sdca_function_device.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2024 Intel Corporation.
+
+/*
+ * SDCA Function Device management
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/soundwire/sdw.h>
+#include <sound/sdca.h>
+#include <sound/sdca_function.h>
+#include "sdca_function_device.h"
+
+/*
+ * A SoundWire device can have multiple SDCA functions identified by
+ * their type and ADR. there can be multiple SoundWire devices per
+ * link, or multiple devices spread across multiple links. An IDA is
+ * required to identify each instance.
+ */
+static DEFINE_IDA(sdca_function_ida);
+
+static void sdca_dev_release(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct sdca_dev *sdev = auxiliary_dev_to_sdca_dev(auxdev);
+
+ ida_free(&sdca_function_ida, auxdev->id);
+ kfree(sdev);
+}
+
+/* alloc, init and add link devices */
+static struct sdca_dev *sdca_dev_register(struct device *parent,
+ struct sdca_function_desc *function_desc)
+{
+ struct sdca_dev *sdev;
+ struct auxiliary_device *auxdev;
+ int ret;
+ int rc;
+
+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return ERR_PTR(-ENOMEM);
+
+ auxdev = &sdev->auxdev;
+ auxdev->name = function_desc->name;
+ auxdev->dev.parent = parent;
+ auxdev->dev.fwnode = function_desc->node;
+ auxdev->dev.release = sdca_dev_release;
+
+ sdev->function.desc = function_desc;
+
+ rc = ida_alloc(&sdca_function_ida, GFP_KERNEL);
+ if (rc < 0) {
+ kfree(sdev);
+ return ERR_PTR(rc);
+ }
+ auxdev->id = rc;
+
+ /* now follow the two-step init/add sequence */
+ ret = auxiliary_device_init(auxdev);
+ if (ret < 0) {
+ dev_err(parent, "failed to initialize SDCA function dev %s\n",
+ function_desc->name);
+ ida_free(&sdca_function_ida, auxdev->id);
+ kfree(sdev);
+ return ERR_PTR(ret);
+ }
+
+ ret = auxiliary_device_add(auxdev);
+ if (ret < 0) {
+ dev_err(parent, "failed to add SDCA function dev %s\n",
+ sdev->auxdev.name);
+ /* sdev will be freed with the put_device() and .release sequence */
+ auxiliary_device_uninit(&sdev->auxdev);
+ return ERR_PTR(ret);
+ }
+
+ return sdev;
+}
+
+static void sdca_dev_unregister(struct sdca_dev *sdev)
+{
+ auxiliary_device_delete(&sdev->auxdev);
+ auxiliary_device_uninit(&sdev->auxdev);
+}
+
+int sdca_dev_register_functions(struct sdw_slave *slave)
+{
+ struct sdca_device_data *sdca_data = &slave->sdca_data;
+ int i;
+
+ for (i = 0; i < sdca_data->num_functions; i++) {
+ struct sdca_dev *func_dev;
+
+ func_dev = sdca_dev_register(&slave->dev,
+ &sdca_data->function[i]);
+ if (!func_dev)
+ return -ENODEV;
+
+ sdca_data->function[i].func_dev = func_dev;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(sdca_dev_register_functions, "SND_SOC_SDCA");
+
+void sdca_dev_unregister_functions(struct sdw_slave *slave)
+{
+ struct sdca_device_data *sdca_data = &slave->sdca_data;
+ int i;
+
+ for (i = 0; i < sdca_data->num_functions; i++)
+ sdca_dev_unregister(sdca_data->function[i].func_dev);
+}
+EXPORT_SYMBOL_NS(sdca_dev_unregister_functions, "SND_SOC_SDCA");
diff --git a/sound/soc/sdca/sdca_function_device.h b/sound/soc/sdca/sdca_function_device.h
new file mode 100644
index 000000000000..5adf7551d3a4
--- /dev/null
+++ b/sound/soc/sdca/sdca_function_device.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* Copyright(c) 2024 Intel Corporation. */
+
+#ifndef __SDCA_FUNCTION_DEVICE_H
+#define __SDCA_FUNCTION_DEVICE_H
+
+struct sdca_dev {
+ struct auxiliary_device auxdev;
+ struct sdca_function_data function;
+};
+
+#define auxiliary_dev_to_sdca_dev(auxiliary_dev) \
+ container_of(auxiliary_dev, struct sdca_dev, auxdev)
+
+#endif
diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c
index 0fcc8e82041e..e4b3efeb30f0 100644
--- a/sound/soc/sdca/sdca_functions.c
+++ b/sound/soc/sdca/sdca_functions.c
@@ -6,8 +6,6 @@
* https://www.mipi.org/mipi-sdca-v1-0-download
*/
-#define dev_fmt(fmt) "%s: " fmt, __func__
-
#include <linux/acpi.h>
#include <linux/byteorder/generic.h>
#include <linux/cleanup.h>
@@ -1013,10 +1011,10 @@ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *enti
control->type = find_sdca_control_datatype(entity, control);
control->nbits = find_sdca_control_bits(entity, control);
- dev_info(dev, "%s: %s: control %#x mode %#x layers %#x cn %#llx int %d %s\n",
- entity->label, control->label, control->sel,
- control->mode, control->layers, control->cn_list,
- control->interrupt_position, control->deferrable ? "deferrable" : "");
+ dev_dbg(dev, "%s: %s: control %#x mode %#x layers %#x cn %#llx int %d %s\n",
+ entity->label, control->label, control->sel,
+ control->mode, control->layers, control->cn_list,
+ control->interrupt_position, control->deferrable ? "deferrable" : "");
return 0;
}
@@ -1137,9 +1135,9 @@ static int find_sdca_entity_iot(struct device *dev,
if (!ret)
terminal->num_transducer = tmp;
- dev_info(dev, "%s: terminal type %#x ref %#x conn %#x count %d\n",
- entity->label, terminal->type, terminal->reference,
- terminal->connector, terminal->num_transducer);
+ dev_dbg(dev, "%s: terminal type %#x ref %#x conn %#x count %d\n",
+ entity->label, terminal->type, terminal->reference,
+ terminal->connector, terminal->num_transducer);
return 0;
}
@@ -1165,8 +1163,8 @@ static int find_sdca_entity_cs(struct device *dev,
if (!ret)
clock->max_delay = tmp;
- dev_info(dev, "%s: clock type %#x delay %d\n", entity->label,
- clock->type, clock->max_delay);
+ dev_dbg(dev, "%s: clock type %#x delay %d\n", entity->label,
+ clock->type, clock->max_delay);
return 0;
}
@@ -1217,8 +1215,8 @@ static int find_sdca_entity_pde(struct device *dev,
delays[i].to_ps = delay_list[j++];
delays[i].us = delay_list[j++];
- dev_info(dev, "%s: from %#x to %#x delay %dus\n", entity->label,
- delays[i].from_ps, delays[i].to_ps, delays[i].us);
+ dev_dbg(dev, "%s: from %#x to %#x delay %dus\n", entity->label,
+ delays[i].from_ps, delays[i].to_ps, delays[i].us);
}
power->num_max_delay = num_delays;
@@ -1324,7 +1322,7 @@ find_sdca_entity_hide(struct device *dev, struct sdw_slave *sdw,
unsigned char *report_desc = NULL;
ret = fwnode_property_read_u32(entity_node,
- "mipi-sdca-RxUMP-ownership-transition-maxdelay", &delay);
+ "mipi-sdca-RxUMP-ownership-transition-max-delay", &delay);
if (!ret)
hide->max_delay = delay;
@@ -1447,8 +1445,8 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw,
entity->type = tmp;
- dev_info(dev, "%s: entity %#x type %#x\n",
- entity->label, entity->id, entity->type);
+ dev_dbg(dev, "%s: entity %#x type %#x\n",
+ entity->label, entity->id, entity->type);
switch (entity->type) {
case SDCA_ENTITY_TYPE_IT:
@@ -1623,7 +1621,7 @@ static int find_sdca_entity_connection_iot(struct device *dev,
terminal->clock = clock_entity;
- dev_info(dev, "%s -> %s\n", clock_entity->label, entity->label);
+ dev_dbg(dev, "%s -> %s\n", clock_entity->label, entity->label);
fwnode_handle_put(clock_node);
return 0;
@@ -1673,7 +1671,7 @@ static int find_sdca_entity_connection_pde(struct device *dev,
return -EINVAL;
}
- dev_info(dev, "%s -> %s\n", managed[i]->label, entity->label);
+ dev_dbg(dev, "%s -> %s\n", managed[i]->label, entity->label);
}
power->num_managed = num_managed;
@@ -1808,7 +1806,7 @@ static int find_sdca_entity_connection(struct device *dev,
pins[i] = connected_entity;
- dev_info(dev, "%s -> %s\n", connected_entity->label, entity->label);
+ dev_dbg(dev, "%s -> %s\n", connected_entity->label, entity->label);
i++;
fwnode_handle_put(connected_node);
@@ -1893,8 +1891,8 @@ static int find_sdca_cluster_channel(struct device *dev,
channel->relationship = tmp;
- dev_info(dev, "cluster %#x: channel id %#x purpose %#x relationship %#x\n",
- cluster->id, channel->id, channel->purpose, channel->relationship);
+ dev_dbg(dev, "cluster %#x: channel id %#x purpose %#x relationship %#x\n",
+ cluster->id, channel->id, channel->purpose, channel->relationship);
return 0;
}
@@ -2065,7 +2063,7 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw,
return -EINVAL;
}
- dev_info(dev, "fileset: %#x\n", filesets_list[i]);
+ dev_dbg(dev, "fileset: %#x\n", filesets_list[i]);
files = devm_kcalloc(dev, num_entries / mult_fileset,
sizeof(struct sdca_fdl_file), GFP_KERNEL);
@@ -2086,8 +2084,8 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw,
file->file_id = fileset_entries[j++];
file->fdl_offset = fileset_entries[j++];
- dev_info(dev, "file: %#x, vendor: %#x, offset: %#x\n",
- file->file_id, file->vendor_id, file->fdl_offset);
+ dev_dbg(dev, "file: %#x, vendor: %#x, offset: %#x\n",
+ file->file_id, file->vendor_id, file->fdl_offset);
}
set->id = filesets_list[i];
@@ -2130,9 +2128,9 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw,
if (!ret)
function->reset_max_delay = tmp;
- dev_info(dev, "%pfwP: name %s busy delay %dus reset delay %dus\n",
- function->desc->node, function->desc->name,
- function->busy_max_delay, function->reset_max_delay);
+ dev_dbg(dev, "%pfwP: name %s busy delay %dus reset delay %dus\n",
+ function->desc->node, function->desc->name,
+ function->busy_max_delay, function->reset_max_delay);
ret = find_sdca_init_table(dev, function_desc->node, function);
if (ret)
diff --git a/sound/soc/sdca/sdca_hid.c b/sound/soc/sdca/sdca_hid.c
index ad53207b0d62..abbd56a3d297 100644
--- a/sound/soc/sdca/sdca_hid.c
+++ b/sound/soc/sdca/sdca_hid.c
@@ -166,6 +166,3 @@ int sdca_hid_process_report(struct sdca_interrupt *interrupt)
return 0;
}
EXPORT_SYMBOL_NS(sdca_hid_process_report, "SND_SOC_SDCA");
-
-MODULE_LICENSE("Dual BSD/GPL");
-MODULE_DESCRIPTION("SDCA HID library");
diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c
index 5176460416bb..8f6a2adfb6fb 100644
--- a/sound/soc/sdca/sdca_interrupts.c
+++ b/sound/soc/sdca/sdca_interrupts.c
@@ -456,11 +456,8 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap,
else if (!interrupt)
continue;
- switch (entity->type) {
- case SDCA_ENTITY_TYPE_XU:
- if (control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER)
- break;
-
+ switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
+ case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
ret = sdca_irq_data_populate(dev, regmap, NULL,
function, entity,
control, interrupt);
@@ -534,27 +531,22 @@ int sdca_irq_populate(struct sdca_function_data *function,
handler = base_handler;
- switch (entity->type) {
- case SDCA_ENTITY_TYPE_ENTITY_0:
- if (control->sel == SDCA_CTL_ENTITY_0_FUNCTION_STATUS)
- handler = function_status_handler;
+ switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
+ case SDCA_CTL_TYPE_S(ENTITY_0, FUNCTION_STATUS):
+ handler = function_status_handler;
break;
- case SDCA_ENTITY_TYPE_GE:
- if (control->sel == SDCA_CTL_GE_DETECTED_MODE)
- handler = detected_mode_handler;
+ case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
+ handler = detected_mode_handler;
break;
- case SDCA_ENTITY_TYPE_XU:
- if (control->sel == SDCA_CTL_XU_FDL_CURRENTOWNER) {
- ret = sdca_fdl_alloc_state(interrupt);
- if (ret)
- return ret;
+ case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
+ ret = sdca_fdl_alloc_state(interrupt);
+ if (ret)
+ return ret;
- handler = fdl_owner_handler;
- }
+ handler = fdl_owner_handler;
break;
- case SDCA_ENTITY_TYPE_HIDE:
- if (control->sel == SDCA_CTL_HIDE_HIDTX_CURRENTOWNER)
- handler = hid_handler;
+ case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER):
+ handler = hid_handler;
break;
default:
break;
@@ -618,6 +610,3 @@ struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev,
return info;
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA");
-
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("SDCA IRQ library");
diff --git a/sound/soc/sdca/sdca_regmap.c b/sound/soc/sdca/sdca_regmap.c
index 8fa138fca00f..2cca9a9c71ea 100644
--- a/sound/soc/sdca/sdca_regmap.c
+++ b/sound/soc/sdca/sdca_regmap.c
@@ -275,6 +275,49 @@ int sdca_regmap_populate_constants(struct device *dev,
}
EXPORT_SYMBOL_NS(sdca_regmap_populate_constants, "SND_SOC_SDCA");
+static int populate_control_defaults(struct device *dev, struct regmap *regmap,
+ struct sdca_function_data *function,
+ struct sdca_entity *entity,
+ struct sdca_control *control)
+{
+ int i, ret;
+ int cn;
+
+ if (control->mode == SDCA_ACCESS_MODE_DC)
+ return 0;
+
+ if (control->layers & SDCA_ACCESS_LAYER_DEVICE)
+ return 0;
+
+ i = 0;
+ for_each_set_bit(cn, (unsigned long *)&control->cn_list,
+ BITS_PER_TYPE(control->cn_list)) {
+ unsigned int reg, val;
+
+ reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, cn);
+
+ if (control->has_default || control->has_fixed) {
+ ret = regmap_write(regmap, reg, control->values[i]);
+ if (ret) {
+ dev_err(dev, "Failed to write default %#x: %d\n",
+ reg, ret);
+ return ret;
+ }
+
+ i++;
+ } else if (!control->is_volatile) {
+ ret = regmap_read(regmap, reg, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read initial %#x: %d\n",
+ reg, ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
/**
* sdca_regmap_write_defaults - write out DisCo defaults to device
* @dev: Pointer to the device.
@@ -283,14 +326,17 @@ EXPORT_SYMBOL_NS(sdca_regmap_populate_constants, "SND_SOC_SDCA");
*
* This function will write out to the hardware all the DisCo default and
* fixed value controls. This will cause them to be populated into the cache,
- * and subsequent handling can be done through a cache sync.
+ * and subsequent handling can be done through a cache sync. It will also
+ * read any non-volatile registers that don't have defaults/fixed values to
+ * populate those into the cache, this ensures they are available for reads
+ * even when the device is runtime suspended.
*
* Return: Returns zero on success, and a negative error code on failure.
*/
int sdca_regmap_write_defaults(struct device *dev, struct regmap *regmap,
struct sdca_function_data *function)
{
- int i, j, k;
+ int i, j;
int ret;
for (i = 0; i < function->num_entities; i++) {
@@ -298,31 +344,30 @@ int sdca_regmap_write_defaults(struct device *dev, struct regmap *regmap,
for (j = 0; j < entity->num_controls; j++) {
struct sdca_control *control = &entity->controls[j];
- int cn;
- if (control->mode == SDCA_ACCESS_MODE_DC)
- continue;
-
- if (!control->has_default && !control->has_fixed)
- continue;
-
- k = 0;
- for_each_set_bit(cn, (unsigned long *)&control->cn_list,
- BITS_PER_TYPE(control->cn_list)) {
- unsigned int reg;
+ ret = populate_control_defaults(dev, regmap, function,
+ entity, control);
+ if (ret)
+ return ret;
+ }
+ }
- reg = SDW_SDCA_CTL(function->desc->adr, entity->id,
- control->sel, cn);
+ return 0;
+}
+EXPORT_SYMBOL_NS(sdca_regmap_write_defaults, "SND_SOC_SDCA");
- ret = regmap_write(regmap, reg, control->values[k]);
- if (ret)
- return ret;
+int sdca_regmap_write_init(struct device *dev, struct regmap *regmap,
+ struct sdca_function_data *function)
+{
+ struct sdca_init_write *init = function->init_table;
+ int ret, i;
- k++;
- }
- }
+ for (i = 0; i < function->num_init_table; i++) {
+ ret = regmap_write(regmap, init[i].addr, init[i].val);
+ if (ret)
+ return ret;
}
return 0;
}
-EXPORT_SYMBOL_NS(sdca_regmap_write_defaults, "SND_SOC_SDCA");
+EXPORT_SYMBOL_NS(sdca_regmap_write_init, "SND_SOC_SDCA");