diff options
| -rw-r--r-- | include/linux/soundwire/sdw_registers.h | 2 | ||||
| -rw-r--r-- | include/sound/sdca.h | 14 | ||||
| -rw-r--r-- | include/sound/sdca_regmap.h | 2 | ||||
| -rw-r--r-- | sound/soc/sdca/Kconfig | 18 | ||||
| -rw-r--r-- | sound/soc/sdca/Makefile | 10 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_class.c | 304 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_class.h | 37 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_class_function.c | 460 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_fdl.c | 93 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_function_device.c | 117 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_function_device.h | 15 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_functions.c | 52 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_hid.c | 3 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_interrupts.c | 39 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_regmap.c | 89 |
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"); |
