diff options
Diffstat (limited to 'sound/soc/sdca')
| -rw-r--r-- | sound/soc/sdca/Kconfig | 28 | ||||
| -rw-r--r-- | sound/soc/sdca/Makefile | 10 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_asoc.c | 89 | ||||
| -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_device.c | 20 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_fdl.c | 504 | ||||
| -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 | 313 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_hid.c | 59 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_interrupts.c | 280 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_regmap.c | 98 | ||||
| -rw-r--r-- | sound/soc/sdca/sdca_ump.c | 262 |
15 files changed, 2391 insertions, 205 deletions
diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig index 6a3ba43f26bd..fabb69a3450d 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). @@ -25,7 +26,34 @@ config SND_SOC_SDCA_IRQ help This option enables support for SDCA IRQs. +config SND_SOC_SDCA_FDL + bool "SDCA FDL (File DownLoad) support" + depends on SND_SOC_SDCA + default y + help + This option enables support for the File Download using UMP, + typically used for downloading firmware to devices. + config SND_SOC_SDCA_OPTIONAL def_tristate SND_SOC_SDCA || !SND_SOC_SDCA +config SND_SOC_SDCA_CLASS + tristate "SDCA Class Driver" + depends on SOUNDWIRE + depends on HID=y || HID=SND_SOC_SDCA + 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 5e51760cb651..f6b73275d964 100644 --- a/sound/soc/sdca/Makefile +++ b/sound/soc/sdca/Makefile @@ -1,7 +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 +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_asoc.c b/sound/soc/sdca/sdca_asoc.c index c493ec530cc5..2d328bbb95b9 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -115,50 +115,6 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun } EXPORT_SYMBOL_NS(sdca_asoc_count_component, "SND_SOC_SDCA"); -static const char *get_terminal_name(enum sdca_terminal_type type) -{ - switch (type) { - case SDCA_TERM_TYPE_LINEIN_STEREO: - return SDCA_TERM_TYPE_LINEIN_STEREO_NAME; - case SDCA_TERM_TYPE_LINEIN_FRONT_LR: - return SDCA_TERM_TYPE_LINEIN_FRONT_LR_NAME; - case SDCA_TERM_TYPE_LINEIN_CENTER_LFE: - return SDCA_TERM_TYPE_LINEIN_CENTER_LFE_NAME; - case SDCA_TERM_TYPE_LINEIN_SURROUND_LR: - return SDCA_TERM_TYPE_LINEIN_SURROUND_LR_NAME; - case SDCA_TERM_TYPE_LINEIN_REAR_LR: - return SDCA_TERM_TYPE_LINEIN_REAR_LR_NAME; - case SDCA_TERM_TYPE_LINEOUT_STEREO: - return SDCA_TERM_TYPE_LINEOUT_STEREO_NAME; - case SDCA_TERM_TYPE_LINEOUT_FRONT_LR: - return SDCA_TERM_TYPE_LINEOUT_FRONT_LR_NAME; - case SDCA_TERM_TYPE_LINEOUT_CENTER_LFE: - return SDCA_TERM_TYPE_LINEOUT_CENTER_LFE_NAME; - case SDCA_TERM_TYPE_LINEOUT_SURROUND_LR: - return SDCA_TERM_TYPE_LINEOUT_SURROUND_LR_NAME; - case SDCA_TERM_TYPE_LINEOUT_REAR_LR: - return SDCA_TERM_TYPE_LINEOUT_REAR_LR_NAME; - case SDCA_TERM_TYPE_MIC_JACK: - return SDCA_TERM_TYPE_MIC_JACK_NAME; - case SDCA_TERM_TYPE_STEREO_JACK: - return SDCA_TERM_TYPE_STEREO_JACK_NAME; - case SDCA_TERM_TYPE_FRONT_LR_JACK: - return SDCA_TERM_TYPE_FRONT_LR_JACK_NAME; - case SDCA_TERM_TYPE_CENTER_LFE_JACK: - return SDCA_TERM_TYPE_CENTER_LFE_JACK_NAME; - case SDCA_TERM_TYPE_SURROUND_LR_JACK: - return SDCA_TERM_TYPE_SURROUND_LR_JACK_NAME; - case SDCA_TERM_TYPE_REAR_LR_JACK: - return SDCA_TERM_TYPE_REAR_LR_JACK_NAME; - case SDCA_TERM_TYPE_HEADPHONE_JACK: - return SDCA_TERM_TYPE_HEADPHONE_JACK_NAME; - case SDCA_TERM_TYPE_HEADSET_JACK: - return SDCA_TERM_TYPE_HEADSET_JACK_NAME; - default: - return NULL; - } -} - static int entity_early_parse_ge(struct device *dev, struct sdca_function_data *function, struct sdca_entity *entity) @@ -217,7 +173,7 @@ static int entity_early_parse_ge(struct device *dev, type = sdca_range(range, SDCA_SELECTED_MODE_TERM_TYPE, i); values[i + 3] = sdca_range(range, SDCA_SELECTED_MODE_INDEX, i); - texts[i + 3] = get_terminal_name(type); + texts[i + 3] = sdca_find_terminal_name(type); if (!texts[i + 3]) { dev_err(dev, "%s: unrecognised terminal type: %#x\n", entity->label, type); @@ -337,7 +293,7 @@ static int entity_parse_ot(struct device *dev, static int entity_pde_event(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kctl, int event) { - struct snd_soc_component *component = widget->dapm->component; + struct snd_soc_component *component = snd_soc_dapm_to_component(widget->dapm); struct sdca_entity *entity = widget->priv; static const int polls = 100; unsigned int reg, val; @@ -499,7 +455,7 @@ static int entity_parse_su_device(struct device *dev, return -EINVAL; } - add_route(route, entity->label, get_terminal_name(term), + add_route(route, entity->label, sdca_find_terminal_name(term), entity->sources[affected->val - 1]->label); } } @@ -655,7 +611,7 @@ static int entity_parse_mu(struct device *dev, static int entity_cs_event(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kctl, int event) { - struct snd_soc_component *component = widget->dapm->component; + struct snd_soc_component *component = snd_soc_dapm_to_component(widget->dapm); struct sdca_entity *entity = widget->priv; if (!component) @@ -795,7 +751,6 @@ static int control_limit_kctl(struct device *dev, struct sdca_control_range *range; int min, max, step; unsigned int *tlv; - int shift; if (control->type != SDCA_CTL_DATATYPE_Q7P8DB) return 0; @@ -814,37 +769,22 @@ static int control_limit_kctl(struct device *dev, min = sign_extend32(min, control->nbits - 1); max = sign_extend32(max, control->nbits - 1); - /* - * FIXME: Only support power of 2 step sizes as this can be supported - * by a simple shift. - */ - if (hweight32(step) != 1) { - dev_err(dev, "%s: %s: currently unsupported step size\n", - entity->label, control->label); - return -EINVAL; - } - - /* - * The SDCA volumes are in steps of 1/256th of a dB, a step down of - * 64 (shift of 6) gives 1/4dB. 1/4dB is the smallest unit that is also - * representable in the ALSA TLVs which are in 1/100ths of a dB. - */ - shift = max(ffs(step) - 1, 6); - tlv = devm_kcalloc(dev, 4, sizeof(*tlv), GFP_KERNEL); if (!tlv) return -ENOMEM; - tlv[0] = SNDRV_CTL_TLVT_DB_SCALE; + tlv[0] = SNDRV_CTL_TLVT_DB_MINMAX; tlv[1] = 2 * sizeof(*tlv); tlv[2] = (min * 100) >> 8; - tlv[3] = ((1 << shift) * 100) >> 8; + tlv[3] = (max * 100) >> 8; + + step = (step * 100) >> 8; - mc->min = min >> shift; - mc->max = max >> shift; - mc->shift = shift; - mc->rshift = shift; - mc->sign_bit = 15 - shift; + mc->min = ((int)tlv[2] / step); + mc->max = ((int)tlv[3] / step); + mc->shift = step; + mc->sign_bit = 15; + mc->sdca_q78 = 1; kctl->tlv.p = tlv; kctl->access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; @@ -902,6 +842,9 @@ static int populate_control(struct device *dev, mc->min = 0; mc->max = clamp((0x1ull << control->nbits) - 1, 0, type_max(mc->max)); + if (SDCA_CTL_TYPE(entity->type, control->sel) == SDCA_CTL_TYPE_S(FU, MUTE)) + mc->invert = true; + (*kctl)->name = control_name; (*kctl)->private_value = (unsigned long)mc; (*kctl)->iface = SNDRV_CTL_ELEM_IFACE_MIXER; 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_device.c b/sound/soc/sdca/sdca_device.c index 4798ce2c8f0b..405e80b979de 100644 --- a/sound/soc/sdca/sdca_device.c +++ b/sound/soc/sdca/sdca_device.c @@ -7,6 +7,7 @@ */ #include <linux/acpi.h> +#include <linux/device.h> #include <linux/dmi.h> #include <linux/module.h> #include <linux/property.h> @@ -27,6 +28,25 @@ void sdca_lookup_interface_revision(struct sdw_slave *slave) } EXPORT_SYMBOL_NS(sdca_lookup_interface_revision, "SND_SOC_SDCA"); +static void devm_acpi_table_put(void *ptr) +{ + acpi_put_table((struct acpi_table_header *)ptr); +} + +void sdca_lookup_swft(struct sdw_slave *slave) +{ + acpi_status status; + + status = acpi_get_table(ACPI_SIG_SWFT, 0, + (struct acpi_table_header **)&slave->sdca_data.swft); + if (ACPI_FAILURE(status)) + dev_info(&slave->dev, "SWFT not available\n"); + else + devm_add_action_or_reset(&slave->dev, devm_acpi_table_put, + &slave->sdca_data.swft); +} +EXPORT_SYMBOL_NS(sdca_lookup_swft, "SND_SOC_SDCA"); + static bool sdca_device_quirk_rt712_vb(struct sdw_slave *slave) { struct sdw_slave_id *id = &slave->id; diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c new file mode 100644 index 000000000000..3180ebd07c40 --- /dev/null +++ b/sound/soc/sdca/sdca_fdl.c @@ -0,0 +1,504 @@ +// 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/acpi.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/dmi.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/sprintf.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <sound/sdca.h> +#include <sound/sdca_fdl.h> +#include <sound/sdca_function.h> +#include <sound/sdca_interrupts.h> +#include <sound/sdca_ump.h> + +/** + * sdca_reset_function - send an SDCA function reset + * @dev: Device pointer for error messages. + * @function: Pointer to the SDCA Function. + * @regmap: Pointer to the SDCA Function regmap. + * + * Return: Zero on success or a negative error code. + */ +int sdca_reset_function(struct device *dev, struct sdca_function_data *function, + struct regmap *regmap) +{ + unsigned int reg = SDW_SDCA_CTL(function->desc->adr, + SDCA_ENTITY_TYPE_ENTITY_0, + SDCA_CTL_ENTITY_0_FUNCTION_ACTION, 0); + unsigned int val, poll_us; + int ret; + + ret = regmap_write(regmap, reg, SDCA_CTL_ENTITY_0_RESET_FUNCTION_NOW); + if (ret) // Allowed for function reset to not be implemented + return 0; + + if (!function->reset_max_delay) { + dev_err(dev, "No reset delay specified in DisCo\n"); + 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, + function->reset_max_delay); + if (ret) { + dev_err(dev, "Failed waiting for function reset: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS(sdca_reset_function, "SND_SOC_SDCA"); + +/** + * sdca_fdl_sync - wait for a function to finish FDL + * @dev: Device pointer for error messages. + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + * + * Return: Zero on success or a negative error code. + */ +int sdca_fdl_sync(struct device *dev, struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + static const int fdl_retries = 6; + unsigned long begin_timeout = msecs_to_jiffies(100); + unsigned long done_timeout = msecs_to_jiffies(4000); + int nfdl; + int i, j; + + for (i = 0; i < fdl_retries; i++) { + nfdl = 0; + + for (j = 0; j < SDCA_MAX_INTERRUPTS; j++) { + struct sdca_interrupt *interrupt = &info->irqs[j]; + struct fdl_state *fdl_state; + unsigned long time; + + if (interrupt->function != function || + !interrupt->entity || !interrupt->control || + interrupt->entity->type != SDCA_ENTITY_TYPE_XU || + interrupt->control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER) + continue; + + fdl_state = interrupt->priv; + nfdl++; + + /* + * Looking for timeout without any new FDL requests + * to imply the device has completed initial + * firmware setup. Alas the specification doesn't + * have any mechanism to detect this. + */ + time = wait_for_completion_timeout(&fdl_state->begin, + begin_timeout); + if (!time) { + dev_dbg(dev, "no new FDL starts\n"); + nfdl--; + continue; + } + + time = wait_for_completion_timeout(&fdl_state->done, + done_timeout); + if (!time) { + dev_err(dev, "timed out waiting for FDL to complete\n"); + goto error; + } + } + + if (!nfdl) + return 0; + } + + dev_err(dev, "too many FDL requests\n"); + +error: + for (j = 0; j < SDCA_MAX_INTERRUPTS; j++) { + struct sdca_interrupt *interrupt = &info->irqs[j]; + struct fdl_state *fdl_state; + + if (interrupt->function != function || + !interrupt->entity || !interrupt->control || + interrupt->entity->type != SDCA_ENTITY_TYPE_XU || + interrupt->control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER) + continue; + + disable_irq(interrupt->irq); + + fdl_state = interrupt->priv; + + sdca_ump_cancel_timeout(&fdl_state->timeout); + } + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_NS_GPL(sdca_fdl_sync, "SND_SOC_SDCA"); + +static char *fdl_get_sku_filename(struct device *dev, + struct sdca_fdl_file *fdl_file) +{ + struct device *parent = dev; + const char *product_vendor; + const char *product_sku; + + /* + * Try to find pci_dev manually because the card may not be ready to be + * used for snd_soc_card_get_pci_ssid yet + */ + while (parent) { + if (dev_is_pci(parent)) { + struct pci_dev *pci_dev = to_pci_dev(parent); + + return kasprintf(GFP_KERNEL, "sdca/%x/%x/%x/%x.bin", + fdl_file->vendor_id, + pci_dev->subsystem_vendor, + pci_dev->subsystem_device, + fdl_file->file_id); + } else { + parent = parent->parent; + } + } + + product_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + if (!product_vendor || !strcmp(product_vendor, "Default string")) + product_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + if (!product_vendor || !strcmp(product_vendor, "Default string")) + product_vendor = dmi_get_system_info(DMI_CHASSIS_VENDOR); + if (!product_vendor) + product_vendor = "unknown"; + + product_sku = dmi_get_system_info(DMI_PRODUCT_SKU); + if (!product_sku || !strcmp(product_sku, "Default string")) + product_sku = dmi_get_system_info(DMI_PRODUCT_NAME); + if (!product_sku) + product_sku = "unknown"; + + return kasprintf(GFP_KERNEL, "sdca/%x/%s/%s/%x.bin", fdl_file->vendor_id, + product_vendor, product_sku, fdl_file->file_id); +} + +static int fdl_load_file(struct sdca_interrupt *interrupt, + struct sdca_fdl_set *set, int file_index) +{ + struct device *dev = interrupt->dev; + struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; + const struct firmware *firmware = NULL; + struct acpi_sw_file *swf = NULL, *tmp; + struct sdca_fdl_file *fdl_file; + char *disk_filename; + int ret; + int i; + + if (!set) { + dev_err(dev, "request to load SWF with no set\n"); + return -EINVAL; + } + + fdl_file = &set->files[file_index]; + + if (fdl_data->swft) { + tmp = fdl_data->swft->files; + for (i = 0; i < fdl_data->swft->header.length; i += tmp->file_length, + tmp = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length)) { + if (tmp->vendor_id == fdl_file->vendor_id && + tmp->file_id == fdl_file->file_id) { + dev_dbg(dev, "located SWF in ACPI: %x-%x-%x\n", + tmp->vendor_id, tmp->file_id, + tmp->file_version); + swf = tmp; + break; + } + } + } + + disk_filename = fdl_get_sku_filename(dev, fdl_file); + if (!disk_filename) + return -ENOMEM; + + dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); + + ret = firmware_request_nowarn(&firmware, disk_filename, dev); + kfree(disk_filename); + if (ret) { + disk_filename = kasprintf(GFP_KERNEL, "sdca/%x/%x.bin", + fdl_file->vendor_id, fdl_file->file_id); + if (!disk_filename) + return -ENOMEM; + + dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); + + ret = firmware_request_nowarn(&firmware, disk_filename, dev); + kfree(disk_filename); + } + + if (!ret) { + tmp = (struct acpi_sw_file *)&firmware->data[0]; + + if (firmware->size < sizeof(*tmp) || + tmp->file_length != firmware->size) { + dev_err(dev, "bad disk SWF size\n"); + } else if (!swf || swf->file_version <= tmp->file_version) { + dev_dbg(dev, "using SWF from disk: %x-%x-%x\n", + tmp->vendor_id, tmp->file_id, tmp->file_version); + swf = tmp; + } + } + + if (!swf) { + dev_err(dev, "failed to locate SWF\n"); + return -ENOENT; + } + + ret = sdca_ump_write_message(dev, interrupt->device_regmap, + interrupt->function_regmap, + interrupt->function, interrupt->entity, + SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, + SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, + swf->file_length - offsetof(struct acpi_sw_file, data)); + release_firmware(firmware); + return ret; +} + +static struct sdca_fdl_set *fdl_get_set(struct sdca_interrupt *interrupt) +{ + struct device *dev = interrupt->dev; + struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; + struct sdca_entity *xu = interrupt->entity; + struct sdca_control_range *range; + unsigned int val; + int i, ret; + + ret = regmap_read(interrupt->function_regmap, + SDW_SDCA_CTL(interrupt->function->desc->adr, xu->id, + SDCA_CTL_XU_FDL_SET_INDEX, 0), + &val); + if (ret < 0) { + dev_err(dev, "failed to read FDL set index: %d\n", ret); + return NULL; + } + + range = sdca_selector_find_range(dev, xu, SDCA_CTL_XU_FDL_SET_INDEX, + SDCA_FDL_SET_INDEX_NCOLS, 0); + + val = sdca_range_search(range, SDCA_FDL_SET_INDEX_SET_NUMBER, + val, SDCA_FDL_SET_INDEX_FILE_SET_ID); + + for (i = 0; i < fdl_data->num_sets; i++) { + if (fdl_data->sets[i].id == val) + return &fdl_data->sets[i]; + } + + dev_err(dev, "invalid fileset id: %d\n", val); + return NULL; +} + +static void fdl_end(struct sdca_interrupt *interrupt) +{ + struct fdl_state *fdl_state = interrupt->priv; + + if (!fdl_state->set) + return; + + fdl_state->set = NULL; + + pm_runtime_put(interrupt->dev); + complete(&fdl_state->done); + + dev_dbg(interrupt->dev, "completed FDL process\n"); +} + +static void sdca_fdl_timeout_work(struct work_struct *work) +{ + struct fdl_state *fdl_state = container_of(work, struct fdl_state, + timeout.work); + struct sdca_interrupt *interrupt = fdl_state->interrupt; + struct device *dev = interrupt->dev; + + dev_err(dev, "FDL transaction timed out\n"); + + guard(mutex)(&fdl_state->lock); + + fdl_end(interrupt); + sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); +} + +static int fdl_status_process(struct sdca_interrupt *interrupt, unsigned int status) +{ + struct fdl_state *fdl_state = interrupt->priv; + int ret; + + switch (status) { + case SDCA_CTL_XU_FDLD_NEEDS_SET: + dev_dbg(interrupt->dev, "starting FDL process...\n"); + + pm_runtime_get(interrupt->dev); + complete(&fdl_state->begin); + + fdl_state->file_index = 0; + fdl_state->set = fdl_get_set(interrupt); + fallthrough; + case SDCA_CTL_XU_FDLD_MORE_FILES_OK: + ret = fdl_load_file(interrupt, fdl_state->set, fdl_state->file_index); + if (ret) { + fdl_end(interrupt); + return SDCA_CTL_XU_FDLH_REQ_ABORT; + } + + return SDCA_CTL_XU_FDLH_FILE_AVAILABLE; + case SDCA_CTL_XU_FDLD_FILE_OK: + if (!fdl_state->set) { + fdl_end(interrupt); + return SDCA_CTL_XU_FDLH_REQ_ABORT; + } + + fdl_state->file_index++; + + if (fdl_state->file_index < fdl_state->set->num_files) + return SDCA_CTL_XU_FDLH_MORE_FILES; + fallthrough; + case SDCA_CTL_XU_FDLD_COMPLETE: + fdl_end(interrupt); + return SDCA_CTL_XU_FDLH_COMPLETE; + default: + fdl_end(interrupt); + + if (status & SDCA_CTL_XU_FDLD_REQ_RESET) + return SDCA_CTL_XU_FDLH_RESET_ACK; + else if (status & SDCA_CTL_XU_FDLD_REQ_ABORT) + return SDCA_CTL_XU_FDLH_COMPLETE; + + dev_err(interrupt->dev, "invalid FDL status: %x\n", status); + return -EINVAL; + } +} + +/** + * sdca_fdl_process - Process the FDL state machine + * @interrupt: SDCA interrupt structure + * + * Based on section 13.2.5 Flow Diagram for File Download, Host side. + * + * Return: Zero on success or a negative error code. + */ +int sdca_fdl_process(struct sdca_interrupt *interrupt) +{ + struct device *dev = interrupt->dev; + struct sdca_entity_xu *xu = &interrupt->entity->xu; + struct fdl_state *fdl_state = interrupt->priv; + unsigned int reg, status; + int response, ret; + + ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap, + interrupt->function, interrupt->entity, + interrupt->control); + if (ret) + goto reset_function; + + sdca_ump_cancel_timeout(&fdl_state->timeout); + + 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; + } + + dev_dbg(dev, "FDL status: %#x\n", status); + + ret = fdl_status_process(interrupt, status); + if (ret < 0) + goto reset_function; + + response = ret; + + dev_dbg(dev, "FDL response: %#x\n", response); + + 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; + } + + 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; + default: + sdca_ump_schedule_timeout(&fdl_state->timeout, xu->max_delay); + return 0; + } + } + +reset_function: + sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(sdca_fdl_process, "SND_SOC_SDCA"); + +/** + * sdca_fdl_alloc_state - allocate state for an FDL interrupt + * @interrupt: SDCA interrupt structure. + * + * Return: Zero on success or a negative error code. + */ +int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) +{ + struct device *dev = interrupt->dev; + struct fdl_state *fdl_state; + + fdl_state = devm_kzalloc(dev, sizeof(struct fdl_state), GFP_KERNEL); + if (!fdl_state) + return -ENOMEM; + + INIT_DELAYED_WORK(&fdl_state->timeout, sdca_fdl_timeout_work); + init_completion(&fdl_state->begin); + init_completion(&fdl_state->done); + mutex_init(&fdl_state->lock); + fdl_state->interrupt = interrupt; + + interrupt->priv = fdl_state; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_fdl_alloc_state, "SND_SOC_SDCA"); diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c new file mode 100644 index 000000000000..c6cc880a150e --- /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 (IS_ERR(func_dev)) + return PTR_ERR(func_dev); + + 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 13f68f7b6dd6..5a1f120487ef 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> @@ -16,6 +14,7 @@ #include <linux/module.h> #include <linux/property.h> #include <linux/soundwire/sdw.h> +#include <linux/string.h> #include <linux/types.h> #include <sound/sdca.h> #include <sound/sdca_function.h> @@ -79,6 +78,8 @@ static const char *get_sdca_function_name(u32 function_type) return SDCA_FUNCTION_TYPE_SPEAKER_MIC_NAME; case SDCA_FUNCTION_TYPE_RJ: return SDCA_FUNCTION_TYPE_RJ_NAME; + case SDCA_FUNCTION_TYPE_COMPANION_AMP: + return SDCA_FUNCTION_TYPE_COMPANION_AMP_NAME; case SDCA_FUNCTION_TYPE_IMP_DEF: return SDCA_FUNCTION_TYPE_IMP_DEF_NAME; default: @@ -179,11 +180,11 @@ static int find_sdca_function(struct acpi_device *adev, void *data) */ void sdca_lookup_functions(struct sdw_slave *slave) { - struct device *dev = &slave->dev; - struct acpi_device *adev = to_acpi_device_node(dev->fwnode); + struct device *sdev = &slave->dev; + struct acpi_device *adev = to_acpi_device_node(sdev->fwnode); if (!adev) { - dev_info(dev, "no matching ACPI device found, ignoring peripheral\n"); + dev_info(sdev, "no matching ACPI device found, ignoring peripheral\n"); return; } @@ -779,6 +780,62 @@ find_sdca_control_datatype(const struct sdca_entity *entity, } } +static bool find_sdca_control_volatile(const struct sdca_entity *entity, + const struct sdca_control *control) +{ + switch (control->mode) { + case SDCA_ACCESS_MODE_DC: + return false; + case SDCA_ACCESS_MODE_RO: + case SDCA_ACCESS_MODE_RW1S: + case SDCA_ACCESS_MODE_RW1C: + return true; + default: + break; + } + + switch (SDCA_CTL_TYPE(entity->type, control->sel)) { + case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): + case SDCA_CTL_TYPE_S(XU, FDL_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(XU, FDL_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(XU, FDL_STATUS): + case SDCA_CTL_TYPE_S(XU, FDL_HOST_REQUEST): + case SDCA_CTL_TYPE_S(SPE, AUTHTX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SPE, AUTHTX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SPE, AUTHTX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SPE, AUTHRX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SPE, AUTHRX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SPE, AUTHRX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(MFPU, AE_CURRENTOWNER): + case SDCA_CTL_TYPE_S(MFPU, AE_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(MFPU, AE_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SMPU, HIST_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SMPU, HIST_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SMPU, HIST_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SMPU, DTODTX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SMPU, DTODTX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SMPU, DTODTX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SMPU, DTODRX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SMPU, DTODRX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SMPU, DTODRX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SAPU, DTODTX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SAPU, DTODTX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SAPU, DTODTX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SAPU, DTODRX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SAPU, DTODRX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SAPU, DTODRX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(HIDE, HIDTX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(HIDE, HIDTX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(HIDE, HIDRX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(HIDE, HIDRX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(HIDE, HIDRX_MESSAGELENGTH): + return true; + default: + return false; + } +} + static int find_sdca_control_range(struct device *dev, struct fwnode_handle *control_node, struct sdca_control_range *range) @@ -894,7 +951,8 @@ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *enti return ret; } - control->values = devm_kzalloc(dev, hweight64(control->cn_list), GFP_KERNEL); + control->values = devm_kcalloc(dev, hweight64(control->cn_list), + sizeof(int), GFP_KERNEL); if (!control->values) return -ENOMEM; @@ -930,6 +988,8 @@ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *enti break; } + control->is_volatile = find_sdca_control_volatile(entity, control); + ret = find_sdca_control_range(dev, control_node, &control->range); if (ret) { dev_err(dev, "%s: control %#x: range missing: %d\n", @@ -952,10 +1012,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; } @@ -1061,6 +1121,14 @@ static int find_sdca_entity_iot(struct device *dev, terminal->type = tmp; terminal->is_dataport = find_sdca_iot_dataport(terminal); + if (!terminal->is_dataport) { + const char *type_name = sdca_find_terminal_name(terminal->type); + + if (type_name) + entity->label = devm_kasprintf(dev, GFP_KERNEL, "%s %s", + entity->label, type_name); + } + ret = fwnode_property_read_u32(entity_node, "mipi-sdca-terminal-reference-number", &tmp); if (!ret) @@ -1076,9 +1144,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; } @@ -1104,8 +1172,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; } @@ -1156,8 +1224,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; @@ -1253,7 +1321,8 @@ bad_list: } static int -find_sdca_entity_hide(struct device *dev, struct fwnode_handle *function_node, +find_sdca_entity_hide(struct device *dev, struct sdw_slave *sdw, + struct fwnode_handle *function_node, struct fwnode_handle *entity_node, struct sdca_entity *entity) { struct sdca_entity_hide *hide = &entity->hide; @@ -1262,7 +1331,7 @@ find_sdca_entity_hide(struct device *dev, struct fwnode_handle *function_node, 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; @@ -1328,7 +1397,7 @@ find_sdca_entity_hide(struct device *dev, struct fwnode_handle *function_node, report_desc, nval); /* add HID device */ - ret = sdca_add_hid_device(dev, entity); + ret = sdca_add_hid_device(dev, sdw, entity); if (ret) { dev_err(dev, "%pfwP: failed to add HID device: %d\n", entity_node, ret); return ret; @@ -1339,7 +1408,29 @@ find_sdca_entity_hide(struct device *dev, struct fwnode_handle *function_node, return 0; } -static int find_sdca_entity(struct device *dev, +static int find_sdca_entity_xu(struct device *dev, + struct fwnode_handle *entity_node, + struct sdca_entity *entity) +{ + struct sdca_entity_xu *xu = &entity->xu; + u32 tmp; + int ret; + + ret = fwnode_property_read_u32(entity_node, + "mipi-sdca-RxUMP-ownership-transition-max-delay", + &tmp); + if (!ret) + xu->max_delay = tmp; + + ret = fwnode_property_read_u32(entity_node, "mipi-sdca-FDL-reset-mechanism", + &tmp); + if (!ret) + xu->reset_mechanism = tmp; + + return 0; +} + +static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, struct fwnode_handle *function_node, struct fwnode_handle *entity_node, struct sdca_entity *entity) @@ -1363,14 +1454,17 @@ static int find_sdca_entity(struct device *dev, 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: case SDCA_ENTITY_TYPE_OT: ret = find_sdca_entity_iot(dev, entity_node, entity); break; + case SDCA_ENTITY_TYPE_XU: + ret = find_sdca_entity_xu(dev, entity_node, entity); + break; case SDCA_ENTITY_TYPE_CS: ret = find_sdca_entity_cs(dev, entity_node, entity); break; @@ -1381,7 +1475,8 @@ static int find_sdca_entity(struct device *dev, ret = find_sdca_entity_ge(dev, entity_node, entity); break; case SDCA_ENTITY_TYPE_HIDE: - ret = find_sdca_entity_hide(dev, function_node, entity_node, entity); + ret = find_sdca_entity_hide(dev, sdw, function_node, + entity_node, entity); break; default: break; @@ -1396,7 +1491,7 @@ static int find_sdca_entity(struct device *dev, return 0; } -static int find_sdca_entities(struct device *dev, +static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, struct fwnode_handle *function_node, struct sdca_function_data *function) { @@ -1448,7 +1543,8 @@ static int find_sdca_entities(struct device *dev, return -EINVAL; } - ret = find_sdca_entity(dev, function_node, entity_node, &entities[i]); + ret = find_sdca_entity(dev, sdw, function_node, + entity_node, &entities[i]); fwnode_handle_put(entity_node); if (ret) return ret; @@ -1478,7 +1574,7 @@ static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data * for (i = 0; i < function->num_entities; i++) { struct sdca_entity *entity = &function->entities[i]; - if (!strcmp(entity->label, entity_label)) + if (!strncmp(entity->label, entity_label, strlen(entity_label))) return entity; } @@ -1534,7 +1630,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; @@ -1584,7 +1680,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; @@ -1719,7 +1815,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); @@ -1804,8 +1900,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; } @@ -1924,15 +2020,105 @@ static int find_sdca_clusters(struct device *dev, return 0; } +static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, + struct fwnode_handle *function_node, + struct sdca_function_data *function) +{ + static const int mult_fileset = 3; + char fileset_name[SDCA_PROPERTY_LENGTH]; + u32 *filesets_list __free(kfree) = NULL; + struct sdca_fdl_set *sets; + int num_sets; + int i, j; + + num_sets = fwnode_property_count_u32(function_node, + "mipi-sdca-file-set-id-list"); + if (num_sets == 0 || num_sets == -EINVAL) { + return 0; + } else if (num_sets < 0) { + dev_err(dev, "%pfwP: failed to read file set list: %d\n", + function_node, num_sets); + return num_sets; + } + + filesets_list = kcalloc(num_sets, sizeof(u32), GFP_KERNEL); + if (!filesets_list) + return -ENOMEM; + + fwnode_property_read_u32_array(function_node, "mipi-sdca-file-set-id-list", + filesets_list, num_sets); + + sets = devm_kcalloc(dev, num_sets, sizeof(struct sdca_fdl_set), GFP_KERNEL); + if (!sets) + return -ENOMEM; + + for (i = 0; i < num_sets; i++) { + u32 *fileset_entries __free(kfree) = NULL; + struct sdca_fdl_set *set = &sets[i]; + struct sdca_fdl_file *files; + int num_files, num_entries; + + snprintf(fileset_name, sizeof(fileset_name), + "mipi-sdca-file-set-id-0x%X", filesets_list[i]); + + num_entries = fwnode_property_count_u32(function_node, fileset_name); + if (num_entries <= 0) { + dev_err(dev, "%pfwP: file set %d missing entries: %d\n", + function_node, filesets_list[i], num_entries); + return -EINVAL; + } else if (num_entries % mult_fileset != 0) { + dev_err(dev, "%pfwP: file set %d files not multiple of %d\n", + function_node, filesets_list[i], mult_fileset); + return -EINVAL; + } + + dev_dbg(dev, "fileset: %#x\n", filesets_list[i]); + + files = devm_kcalloc(dev, num_entries / mult_fileset, + sizeof(struct sdca_fdl_file), GFP_KERNEL); + if (!files) + return -ENOMEM; + + fileset_entries = kcalloc(num_entries, sizeof(u32), GFP_KERNEL); + if (!fileset_entries) + return -ENOMEM; + + fwnode_property_read_u32_array(function_node, fileset_name, + fileset_entries, num_entries); + + for (j = 0, num_files = 0; j < num_entries; num_files++) { + struct sdca_fdl_file *file = &files[num_files]; + + file->vendor_id = fileset_entries[j++]; + file->file_id = fileset_entries[j++]; + file->fdl_offset = fileset_entries[j++]; + + dev_dbg(dev, "file: %#x, vendor: %#x, offset: %#x\n", + file->file_id, file->vendor_id, file->fdl_offset); + } + + set->id = filesets_list[i]; + set->num_files = num_files; + set->files = files; + } + + function->fdl_data.swft = sdw->sdca_data.swft; + function->fdl_data.num_sets = num_sets; + function->fdl_data.sets = sets; + + return 0; +} + /** * sdca_parse_function - parse ACPI DisCo for a Function * @dev: Pointer to device against which function data will be allocated. + * @sdw: SoundWire slave device to be processed. * @function_desc: Pointer to the Function short descriptor. * @function: Pointer to the Function information, to be populated. * * Return: Returns 0 for success. */ -int sdca_parse_function(struct device *dev, +int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, struct sdca_function_desc *function_desc, struct sdca_function_data *function) { @@ -1946,14 +2132,20 @@ int sdca_parse_function(struct device *dev, if (!ret) function->busy_max_delay = tmp; - dev_info(dev, "%pfwP: name %s delay %dus\n", function->desc->node, - function->desc->name, function->busy_max_delay); + ret = fwnode_property_read_u32(function_desc->node, + "mipi-sdca-function-reset-max-delay", &tmp); + if (!ret) + function->reset_max_delay = tmp; + + 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) return ret; - ret = find_sdca_entities(dev, function_desc->node, function); + ret = find_sdca_entities(dev, sdw, function_desc->node, function); if (ret) return ret; @@ -1965,10 +2157,59 @@ int sdca_parse_function(struct device *dev, if (ret < 0) return ret; + ret = find_sdca_filesets(dev, sdw, function_desc->node, function); + if (ret) + return ret; + return 0; } EXPORT_SYMBOL_NS(sdca_parse_function, "SND_SOC_SDCA"); +const char *sdca_find_terminal_name(enum sdca_terminal_type type) +{ + switch (type) { + case SDCA_TERM_TYPE_LINEIN_STEREO: + return SDCA_TERM_TYPE_LINEIN_STEREO_NAME; + case SDCA_TERM_TYPE_LINEIN_FRONT_LR: + return SDCA_TERM_TYPE_LINEIN_FRONT_LR_NAME; + case SDCA_TERM_TYPE_LINEIN_CENTER_LFE: + return SDCA_TERM_TYPE_LINEIN_CENTER_LFE_NAME; + case SDCA_TERM_TYPE_LINEIN_SURROUND_LR: + return SDCA_TERM_TYPE_LINEIN_SURROUND_LR_NAME; + case SDCA_TERM_TYPE_LINEIN_REAR_LR: + return SDCA_TERM_TYPE_LINEIN_REAR_LR_NAME; + case SDCA_TERM_TYPE_LINEOUT_STEREO: + return SDCA_TERM_TYPE_LINEOUT_STEREO_NAME; + case SDCA_TERM_TYPE_LINEOUT_FRONT_LR: + return SDCA_TERM_TYPE_LINEOUT_FRONT_LR_NAME; + case SDCA_TERM_TYPE_LINEOUT_CENTER_LFE: + return SDCA_TERM_TYPE_LINEOUT_CENTER_LFE_NAME; + case SDCA_TERM_TYPE_LINEOUT_SURROUND_LR: + return SDCA_TERM_TYPE_LINEOUT_SURROUND_LR_NAME; + case SDCA_TERM_TYPE_LINEOUT_REAR_LR: + return SDCA_TERM_TYPE_LINEOUT_REAR_LR_NAME; + case SDCA_TERM_TYPE_MIC_JACK: + return SDCA_TERM_TYPE_MIC_JACK_NAME; + case SDCA_TERM_TYPE_STEREO_JACK: + return SDCA_TERM_TYPE_STEREO_JACK_NAME; + case SDCA_TERM_TYPE_FRONT_LR_JACK: + return SDCA_TERM_TYPE_FRONT_LR_JACK_NAME; + case SDCA_TERM_TYPE_CENTER_LFE_JACK: + return SDCA_TERM_TYPE_CENTER_LFE_JACK_NAME; + case SDCA_TERM_TYPE_SURROUND_LR_JACK: + return SDCA_TERM_TYPE_SURROUND_LR_JACK_NAME; + case SDCA_TERM_TYPE_REAR_LR_JACK: + return SDCA_TERM_TYPE_REAR_LR_JACK_NAME; + case SDCA_TERM_TYPE_HEADPHONE_JACK: + return SDCA_TERM_TYPE_HEADPHONE_JACK_NAME; + case SDCA_TERM_TYPE_HEADSET_JACK: + return SDCA_TERM_TYPE_HEADSET_JACK_NAME; + default: + return NULL; + } +} +EXPORT_SYMBOL_NS(sdca_find_terminal_name, "SND_SOC_SDCA"); + struct sdca_control *sdca_selector_find_control(struct device *dev, struct sdca_entity *entity, const int sel) diff --git a/sound/soc/sdca/sdca_hid.c b/sound/soc/sdca/sdca_hid.c index 967f7ec6fb79..abbd56a3d297 100644 --- a/sound/soc/sdca/sdca_hid.c +++ b/sound/soc/sdca/sdca_hid.c @@ -10,6 +10,7 @@ #include <linux/cleanup.h> #include <linux/device.h> #include <linux/dev_printk.h> +#include <linux/hid.h> #include <linux/module.h> #include <linux/property.h> #include <linux/soundwire/sdw.h> @@ -17,6 +18,8 @@ #include <sound/sdca.h> #include <sound/sdca_function.h> #include <sound/sdca_hid.h> +#include <sound/sdca_interrupts.h> +#include <sound/sdca_ump.h> static int sdwhid_parse(struct hid_device *hid) { @@ -82,15 +85,13 @@ static const struct hid_ll_driver sdw_hid_driver = { .raw_request = sdwhid_raw_request, }; -int sdca_add_hid_device(struct device *dev, struct sdca_entity *entity) +int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + struct sdca_entity *entity) { - struct sdw_bus *bus; + struct sdw_bus *bus = sdw->bus; struct hid_device *hid; - struct sdw_slave *slave = dev_to_sdw_dev(dev); int ret; - bus = slave->bus; - hid = hid_allocate_device(); if (IS_ERR(hid)) return PTR_ERR(hid); @@ -103,8 +104,8 @@ int sdca_add_hid_device(struct device *dev, struct sdca_entity *entity) snprintf(hid->name, sizeof(hid->name), "HID sdw:%01x:%01x:%04x:%04x:%02x", - bus->controller_id, bus->link_id, slave->id.mfg_id, - slave->id.part_id, slave->id.class_id); + bus->controller_id, bus->link_id, sdw->id.mfg_id, + sdw->id.part_id, sdw->id.class_id); snprintf(hid->phys, sizeof(hid->phys), "%s", dev->bus->name); @@ -123,5 +124,45 @@ int sdca_add_hid_device(struct device *dev, struct sdca_entity *entity) } EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); -MODULE_LICENSE("Dual BSD/GPL"); -MODULE_DESCRIPTION("SDCA HID library"); +/** + * sdca_hid_process_report - read a HID event from the device and report + * @interrupt: Pointer to the SDCA interrupt information structure. + * + * Return: Zero on success, and a negative error code on failure. + */ +int sdca_hid_process_report(struct sdca_interrupt *interrupt) +{ + struct device *dev = interrupt->dev; + struct hid_device *hid = interrupt->entity->hide.hid; + void *val __free(kfree) = NULL; + int len, ret; + + ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap, + interrupt->function, interrupt->entity, + interrupt->control); + if (ret) + return ret; + + len = sdca_ump_read_message(dev, interrupt->device_regmap, + interrupt->function_regmap, + interrupt->function, interrupt->entity, + SDCA_CTL_HIDE_HIDTX_MESSAGEOFFSET, + SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH, &val); + if (len < 0) + return len; + + ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap, + interrupt->function, interrupt->entity, + interrupt->control); + if (ret) + return ret; + + ret = hid_input_report(hid, HID_INPUT_REPORT, val, len, true); + if (ret < 0) { + dev_err(dev, "failed to report hid event: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS(sdca_hid_process_report, "SND_SOC_SDCA"); diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 79bf3042f57d..8f6a2adfb6fb 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -11,13 +11,18 @@ #include <linux/bits.h> #include <linux/cleanup.h> #include <linux/device.h> +#include <linux/dev_printk.h> #include <linux/interrupt.h> +#include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_registers.h> #include <sound/sdca.h> +#include <sound/sdca_fdl.h> #include <sound/sdca_function.h> +#include <sound/sdca_hid.h> #include <sound/sdca_interrupts.h> +#include <sound/sdca_ump.h> #include <sound/soc-component.h> #include <sound/soc.h> @@ -75,7 +80,7 @@ static const struct regmap_irq_chip sdca_irq_chip = { static irqreturn_t base_handler(int irq, void *data) { struct sdca_interrupt *interrupt = data; - struct device *dev = interrupt->component->dev; + struct device *dev = interrupt->dev; dev_info(dev, "%s irq without full handling\n", interrupt->name); @@ -85,19 +90,26 @@ static irqreturn_t base_handler(int irq, void *data) static irqreturn_t function_status_handler(int irq, void *data) { struct sdca_interrupt *interrupt = data; - struct device *dev = interrupt->component->dev; + struct device *dev = interrupt->dev; + irqreturn_t irqret = IRQ_NONE; unsigned int reg, val; unsigned long status; unsigned int mask; int ret; + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "failed to resume for function status: %d\n", ret); + goto error; + } + reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, interrupt->control->sel, 0); - ret = regmap_read(interrupt->component->regmap, reg, &val); + ret = regmap_read(interrupt->function_regmap, reg, &val); if (ret < 0) { dev_err(dev, "failed to read function status: %d\n", ret); - return IRQ_NONE; + goto error; } dev_dbg(dev, "function status: %#x\n", val); @@ -127,40 +139,50 @@ static irqreturn_t function_status_handler(int irq, void *data) } } - ret = regmap_write(interrupt->component->regmap, reg, val); + ret = regmap_write(interrupt->function_regmap, reg, val); if (ret < 0) { dev_err(dev, "failed to clear function status: %d\n", ret); - return IRQ_NONE; + goto error; } - return IRQ_HANDLED; + irqret = IRQ_HANDLED; +error: + pm_runtime_put(dev); + return irqret; } static irqreturn_t detected_mode_handler(int irq, void *data) { struct sdca_interrupt *interrupt = data; + struct device *dev = interrupt->dev; struct snd_soc_component *component = interrupt->component; - struct device *dev = component->dev; struct snd_soc_card *card = component->card; struct rw_semaphore *rwsem = &card->snd_card->controls_rwsem; struct snd_kcontrol *kctl = interrupt->priv; struct snd_ctl_elem_value *ucontrol __free(kfree) = NULL; struct soc_enum *soc_enum; + irqreturn_t irqret = IRQ_NONE; unsigned int reg, val; int ret; + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "failed to resume for detected mode: %d\n", ret); + goto error; + } + if (!kctl) { const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%s %s", interrupt->entity->label, SDCA_CTL_SELECTED_MODE_NAME); if (!name) - return IRQ_NONE; + goto error; kctl = snd_soc_component_get_kcontrol(component, name); if (!kctl) { dev_dbg(dev, "control not found: %s\n", name); - return IRQ_NONE; + goto error; } interrupt->priv = kctl; @@ -171,10 +193,10 @@ static irqreturn_t detected_mode_handler(int irq, void *data) reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, interrupt->control->sel, 0); - ret = regmap_read(component->regmap, reg, &val); + ret = regmap_read(interrupt->function_regmap, reg, &val); if (ret < 0) { dev_err(dev, "failed to read detected mode: %d\n", ret); - return IRQ_NONE; + goto error; } switch (val) { @@ -190,12 +212,12 @@ static irqreturn_t detected_mode_handler(int irq, void *data) * detected mode is unknown we need to see what the device * selected as a "safe" option. */ - regcache_drop_region(component->regmap, reg, reg); + regcache_drop_region(interrupt->function_regmap, reg, reg); - ret = regmap_read(component->regmap, reg, &val); + ret = regmap_read(interrupt->function_regmap, reg, &val); if (ret) { dev_err(dev, "failed to re-check selected mode: %d\n", ret); - return IRQ_NONE; + goto error; } break; default: @@ -206,7 +228,7 @@ static irqreturn_t detected_mode_handler(int irq, void *data) ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL); if (!ucontrol) - return IRQ_NONE; + goto error; ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(soc_enum, val); @@ -215,12 +237,61 @@ static irqreturn_t detected_mode_handler(int irq, void *data) up_write(rwsem); if (ret < 0) { dev_err(dev, "failed to update selected mode: %d\n", ret); - return IRQ_NONE; + goto error; } snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); - return IRQ_HANDLED; + irqret = IRQ_HANDLED; +error: + pm_runtime_put(dev); + return irqret; +} + +static irqreturn_t hid_handler(int irq, void *data) +{ + struct sdca_interrupt *interrupt = data; + struct device *dev = interrupt->dev; + irqreturn_t irqret = IRQ_NONE; + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "failed to resume for hid: %d\n", ret); + goto error; + } + + ret = sdca_hid_process_report(interrupt); + if (ret) + goto error; + + irqret = IRQ_HANDLED; +error: + pm_runtime_put(dev); + return irqret; +} + +static irqreturn_t fdl_owner_handler(int irq, void *data) +{ + struct sdca_interrupt *interrupt = data; + struct device *dev = interrupt->dev; + irqreturn_t irqret = IRQ_NONE; + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "failed to resume for fdl: %d\n", ret); + goto error; + } + + ret = sdca_fdl_process(interrupt); + if (ret) + goto error; + + irqret = IRQ_HANDLED; +error: + pm_runtime_put(dev); + return irqret; } static int sdca_irq_request_locked(struct device *dev, @@ -240,6 +311,8 @@ static int sdca_irq_request_locked(struct device *dev, if (ret) return ret; + info->irqs[sdca_irq].irq = irq; + dev_dbg(dev, "requested irq %d for %s\n", irq, name); return 0; @@ -279,14 +352,14 @@ int sdca_irq_request(struct device *dev, struct sdca_interrupt_info *info, return ret; } - info->irqs[sdca_irq].externally_requested = true; - return 0; } EXPORT_SYMBOL_NS_GPL(sdca_irq_request, "SND_SOC_SDCA"); /** * sdca_irq_data_populate - Populate common interrupt data + * @dev: Pointer to the Function device. + * @regmap: Pointer to the Function regmap. * @component: Pointer to the ASoC component for the Function. * @function: Pointer to the SDCA Function. * @entity: Pointer to the SDCA Entity. @@ -295,21 +368,31 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_request, "SND_SOC_SDCA"); * * Return: Zero on success, and a negative error code on failure. */ -int sdca_irq_data_populate(struct snd_soc_component *component, +int sdca_irq_data_populate(struct device *dev, struct regmap *regmap, + struct snd_soc_component *component, struct sdca_function_data *function, struct sdca_entity *entity, struct sdca_control *control, struct sdca_interrupt *interrupt) { - struct device *dev = component->dev; const char *name; + if (!dev && component) + dev = component->dev; + if (!dev) + return -ENODEV; + name = devm_kasprintf(dev, GFP_KERNEL, "%s %s %s", function->desc->name, entity->label, control->label); if (!name) return -ENOMEM; interrupt->name = name; + interrupt->dev = dev; + if (!regmap && component) + interrupt->function_regmap = component->regmap; + else + interrupt->function_regmap = regmap; interrupt->component = component; interrupt->function = function; interrupt->entity = entity; @@ -319,6 +402,92 @@ int sdca_irq_data_populate(struct snd_soc_component *component, } EXPORT_SYMBOL_NS_GPL(sdca_irq_data_populate, "SND_SOC_SDCA"); +static struct sdca_interrupt *get_interrupt_data(struct device *dev, int irq, + struct sdca_interrupt_info *info) +{ + if (irq == SDCA_NO_INTERRUPT) { + return NULL; + } else if (irq < 0 || irq >= SDCA_MAX_INTERRUPTS) { + dev_err(dev, "bad irq position: %d\n", irq); + return ERR_PTR(-EINVAL); + } + + if (info->irqs[irq].irq) { + dev_dbg(dev, "skipping irq %d, already requested\n", irq); + return NULL; + } + + return &info->irqs[irq]; +} + +/** + * sdca_irq_populate_early - process pre-audio card IRQ registrations + * @dev: Device pointer for SDCA Function. + * @regmap: Regmap pointer for the SDCA Function. + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + * + * This is intended to be used as part of the Function boot process. It + * can be called before the soundcard is registered (ie. doesn't depend + * on component) and will register the FDL interrupts. + * + * Return: Zero on success, and a negative error code on failure. + */ +int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, + struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + int i, j; + + guard(mutex)(&info->irq_lock); + + for (i = 0; i < function->num_entities; i++) { + struct sdca_entity *entity = &function->entities[i]; + + for (j = 0; j < entity->num_controls; j++) { + struct sdca_control *control = &entity->controls[j]; + int irq = control->interrupt_position; + struct sdca_interrupt *interrupt; + int ret; + + interrupt = get_interrupt_data(dev, irq, info); + if (IS_ERR(interrupt)) + return PTR_ERR(interrupt); + else if (!interrupt) + continue; + + 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); + if (ret) + return ret; + + ret = sdca_fdl_alloc_state(interrupt); + if (ret) + return ret; + + ret = sdca_irq_request_locked(dev, info, irq, + interrupt->name, + fdl_owner_handler, + interrupt); + if (ret) { + dev_err(dev, "failed to request irq %s: %d\n", + interrupt->name, ret); + return ret; + } + break; + default: + break; + } + } + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_irq_populate_early, "SND_SOC_SDCA"); + /** * sdca_irq_populate - Request all the individual IRQs for an SDCA Function * @function: Pointer to the SDCA Function. @@ -348,37 +517,36 @@ int sdca_irq_populate(struct sdca_function_data *function, irq_handler_t handler; int ret; - if (irq == SDCA_NO_INTERRUPT) { + interrupt = get_interrupt_data(dev, irq, info); + if (IS_ERR(interrupt)) + return PTR_ERR(interrupt); + else if (!interrupt) continue; - } else if (irq < 0 || irq >= SDCA_MAX_INTERRUPTS) { - dev_err(dev, "bad irq position: %d\n", irq); - return -EINVAL; - } - interrupt = &info->irqs[irq]; - - if (interrupt->externally_requested) { - dev_dbg(dev, - "skipping irq %d, externally requested\n", - irq); - continue; - } - - ret = sdca_irq_data_populate(component, function, entity, - control, interrupt); + ret = sdca_irq_data_populate(dev, NULL, component, + function, entity, control, + interrupt); if (ret) return ret; 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_CTL_TYPE_S(GE, DETECTED_MODE): + handler = detected_mode_handler; + break; + case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): + ret = sdca_fdl_alloc_state(interrupt); + if (ret) + return ret; + + handler = fdl_owner_handler; break; - case SDCA_ENTITY_TYPE_GE: - if (control->sel == SDCA_CTL_GE_DETECTED_MODE) - handler = detected_mode_handler; + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): + handler = hid_handler; break; default: break; @@ -400,7 +568,7 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA"); /** * sdca_irq_allocate - allocate an SDCA interrupt structure for a device - * @dev: Device pointer against which things should be allocated. + * @sdev: Device pointer against which things should be allocated. * @regmap: regmap to be used for accessing the SDCA IRQ registers. * @irq: The interrupt number. * @@ -411,34 +579,34 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA"); * Return: A pointer to the allocated sdca_interrupt_info struct, or an * error code. */ -struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev, +struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev, struct regmap *regmap, int irq) { struct sdca_interrupt_info *info; - int ret; + int ret, i; - info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + info = devm_kzalloc(sdev, sizeof(*info), GFP_KERNEL); if (!info) return ERR_PTR(-ENOMEM); info->irq_chip = sdca_irq_chip; - ret = devm_mutex_init(dev, &info->irq_lock); + for (i = 0; i < ARRAY_SIZE(info->irqs); i++) + info->irqs[i].device_regmap = regmap; + + ret = devm_mutex_init(sdev, &info->irq_lock); if (ret) return ERR_PTR(ret); - ret = devm_regmap_add_irq_chip(dev, regmap, irq, IRQF_ONESHOT, 0, + ret = devm_regmap_add_irq_chip(sdev, regmap, irq, IRQF_ONESHOT, 0, &info->irq_chip, &info->irq_data); if (ret) { - dev_err(dev, "failed to register irq chip: %d\n", ret); + dev_err(sdev, "failed to register irq chip: %d\n", ret); return ERR_PTR(ret); } - dev_dbg(dev, "registered on irq %d\n", irq); + dev_dbg(sdev, "registered on irq %d\n", irq); 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 72f893e00ff5..2cca9a9c71ea 100644 --- a/sound/soc/sdca/sdca_regmap.c +++ b/sound/soc/sdca/sdca_regmap.c @@ -147,14 +147,7 @@ bool sdca_regmap_volatile(struct sdca_function_data *function, unsigned int reg) if (!control) return false; - switch (control->mode) { - case SDCA_ACCESS_MODE_RO: - case SDCA_ACCESS_MODE_RW1S: - case SDCA_ACCESS_MODE_RW1C: - return true; - default: - return false; - } + return control->is_volatile; } EXPORT_SYMBOL_NS(sdca_regmap_volatile, "SND_SOC_SDCA"); @@ -282,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. @@ -290,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++) { @@ -305,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"); diff --git a/sound/soc/sdca/sdca_ump.c b/sound/soc/sdca/sdca_ump.c new file mode 100644 index 000000000000..8aba3ff16872 --- /dev/null +++ b/sound/soc/sdca/sdca_ump.c @@ -0,0 +1,262 @@ +// 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/dev_printk.h> +#include <linux/device.h> +#include <linux/regmap.h> +#include <sound/sdca.h> +#include <sound/sdca_function.h> +#include <sound/sdca_ump.h> +#include <sound/soc-component.h> +#include <linux/soundwire/sdw_registers.h> + +/** + * sdca_ump_get_owner_host - check a UMP buffer is owned by the host + * @dev: Pointer to the struct device used for error messages. + * @function_regmap: Pointer to the regmap for the SDCA Function. + * @function: Pointer to the Function information. + * @entity: Pointer to the SDCA Entity. + * @control: Pointer to the SDCA Control for the UMP Owner. + * + * Return: Returns zero on success, and a negative error code on failure. + */ +int sdca_ump_get_owner_host(struct device *dev, + struct regmap *function_regmap, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct sdca_control *control) +{ + unsigned int reg, owner; + int ret; + + reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0); + ret = regmap_read(function_regmap, reg, &owner); + if (ret < 0) { + dev_err(dev, "%s: failed to read UMP owner: %d\n", + entity->label, ret); + return ret; + } + + if (owner != SDCA_UMP_OWNER_HOST) { + dev_err(dev, "%s: host is not the UMP owner\n", entity->label); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_ump_get_owner_host, "SND_SOC_SDCA"); + +/** + * sdca_ump_set_owner_device - set a UMP buffer's ownership back to the device + * @dev: Pointer to the struct device used for error messages. + * @function_regmap: Pointer to the regmap for the SDCA Function. + * @function: Pointer to the Function information. + * @entity: Pointer to the SDCA Entity. + * @control: Pointer to the SDCA Control for the UMP Owner. + * + * Return: Returns zero on success, and a negative error code on failure. + */ +int sdca_ump_set_owner_device(struct device *dev, + struct regmap *function_regmap, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct sdca_control *control) +{ + unsigned int reg; + int ret; + + reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0); + ret = regmap_write(function_regmap, reg, SDCA_UMP_OWNER_DEVICE); + if (ret < 0) + dev_err(dev, "%s: failed to write UMP owner: %d\n", + entity->label, ret); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(sdca_ump_set_owner_device, "SND_SOC_SDCA"); + +/** + * sdca_ump_read_message - read a UMP message from the device + * @dev: Pointer to the struct device used for error messages. + * @device_regmap: Pointer to the Device register map. + * @function_regmap: Pointer to the regmap for the SDCA Function. + * @function: Pointer to the Function information. + * @entity: Pointer to the SDCA Entity. + * @offset_sel: Control Selector for the UMP Offset Control. + * @length_sel: Control Selector for the UMP Length Control. + * @msg: Pointer that will be populated with an dynamically buffer + * containing the UMP message. Note this needs to be freed by the + * caller. + * + * The caller should first call sdca_ump_get_owner_host() to ensure the host + * currently owns the UMP buffer, and then this function can be used to + * retrieve a message. It is the callers responsibility to free the + * message once it is finished with it. Finally sdca_ump_set_owner_device() + * should be called to return the buffer to the device. + * + * Return: Returns the message length on success, and a negative error + * code on failure. + */ +int sdca_ump_read_message(struct device *dev, + struct regmap *device_regmap, + struct regmap *function_regmap, + struct sdca_function_data *function, + struct sdca_entity *entity, + unsigned int offset_sel, unsigned int length_sel, + void **msg) +{ + struct sdca_control_range *range; + unsigned int msg_offset, msg_len; + unsigned int buf_addr, buf_len; + unsigned int reg; + int ret; + + reg = SDW_SDCA_CTL(function->desc->adr, entity->id, offset_sel, 0); + ret = regmap_read(function_regmap, reg, &msg_offset); + if (ret < 0) { + dev_err(dev, "%s: failed to read UMP offset: %d\n", + entity->label, ret); + return ret; + } + + range = sdca_selector_find_range(dev, entity, offset_sel, + SDCA_MESSAGEOFFSET_NCOLS, 1); + if (!range) + return -ENOENT; + + buf_addr = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_START_ADDRESS, 0); + buf_len = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_LENGTH, 0); + + reg = SDW_SDCA_CTL(function->desc->adr, entity->id, length_sel, 0); + ret = regmap_read(function_regmap, reg, &msg_len); + if (ret < 0) { + dev_err(dev, "%s: failed to read UMP length: %d\n", + entity->label, ret); + return ret; + } + + if (msg_len > buf_len - msg_offset) { + dev_err(dev, "%s: message too big for UMP buffer: %d\n", + entity->label, msg_len); + return -EINVAL; + } + + *msg = kmalloc(msg_len, GFP_KERNEL); + if (!*msg) + return -ENOMEM; + + ret = regmap_raw_read(device_regmap, buf_addr + msg_offset, *msg, msg_len); + if (ret < 0) { + dev_err(dev, "%s: failed to read UMP message: %d\n", + entity->label, ret); + return ret; + } + + return msg_len; +} +EXPORT_SYMBOL_NS_GPL(sdca_ump_read_message, "SND_SOC_SDCA"); + +/** + * sdca_ump_write_message - write a UMP message to the device + * @dev: Pointer to the struct device used for error messages. + * @device_regmap: Pointer to the Device register map. + * @function_regmap: Pointer to the regmap for the SDCA Function. + * @function: Pointer to the Function information. + * @entity: Pointer to the SDCA Entity. + * @offset_sel: Control Selector for the UMP Offset Control. + * @msg_offset: Offset within the UMP buffer at which the message should + * be written. + * @length_sel: Control Selector for the UMP Length Control. + * @msg: Pointer to the data that should be written to the UMP buffer. + * @msg_len: Length of the message data in bytes. + * + * The caller should first call sdca_ump_get_owner_host() to ensure the host + * currently owns the UMP buffer, and then this function can be used to + * write a message. Finally sdca_ump_set_owner_device() should be called to + * return the buffer to the device, allowing the device to access the + * message. + * + * Return: Returns zero on success, and a negative error code on failure. + */ +int sdca_ump_write_message(struct device *dev, + struct regmap *device_regmap, + struct regmap *function_regmap, + struct sdca_function_data *function, + struct sdca_entity *entity, + unsigned int offset_sel, unsigned int msg_offset, + unsigned int length_sel, + void *msg, int msg_len) +{ + struct sdca_control_range *range; + unsigned int buf_addr, buf_len, ump_mode; + unsigned int reg; + int ret; + + range = sdca_selector_find_range(dev, entity, offset_sel, + SDCA_MESSAGEOFFSET_NCOLS, 1); + if (!range) + return -ENOENT; + + buf_addr = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_START_ADDRESS, 0); + buf_len = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_LENGTH, 0); + ump_mode = sdca_range(range, SDCA_MESSAGEOFFSET_UMP_MODE, 0); + + if (msg_len > buf_len - msg_offset) { + dev_err(dev, "%s: message too big for UMP buffer: %d\n", + entity->label, msg_len); + return -EINVAL; + } + + if (ump_mode != SDCA_UMP_MODE_DIRECT) { + dev_err(dev, "%s: only direct mode currently supported\n", + entity->label); + return -EINVAL; + } + + ret = regmap_raw_write(device_regmap, buf_addr + msg_offset, msg, msg_len); + if (ret) { + dev_err(dev, "%s: failed to write UMP message: %d\n", + entity->label, ret); + return ret; + } + + reg = SDW_SDCA_CTL(function->desc->adr, entity->id, offset_sel, 0); + ret = regmap_write(function_regmap, reg, msg_offset); + if (ret < 0) { + dev_err(dev, "%s: failed to write UMP offset: %d\n", + entity->label, ret); + return ret; + } + + reg = SDW_SDCA_CTL(function->desc->adr, entity->id, length_sel, 0); + ret = regmap_write(function_regmap, reg, msg_len); + if (ret < 0) { + dev_err(dev, "%s: failed to write UMP length: %d\n", + entity->label, ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_ump_write_message, "SND_SOC_SDCA"); + +void sdca_ump_cancel_timeout(struct delayed_work *work) +{ + cancel_delayed_work_sync(work); +} +EXPORT_SYMBOL_NS_GPL(sdca_ump_cancel_timeout, "SND_SOC_SDCA"); + +void sdca_ump_schedule_timeout(struct delayed_work *work, unsigned int timeout_us) +{ + if (!timeout_us) + return; + + queue_delayed_work(system_wq, work, usecs_to_jiffies(timeout_us)); +} +EXPORT_SYMBOL_NS_GPL(sdca_ump_schedule_timeout, "SND_SOC_SDCA"); |
