diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h index 599a72f3351535..94cf20d21326c4 100644 --- a/include/sound/sdca_asoc.h +++ b/include/sound/sdca_asoc.h @@ -13,6 +13,7 @@ struct device; struct regmap; struct sdca_function_data; +struct sdca_interrupt_info; struct sdca_pde_delay; struct snd_ctl_elem_value; struct snd_kcontrol; @@ -80,6 +81,7 @@ int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *funct int sdca_asoc_populate_component(struct device *dev, struct sdca_function_data *function, + struct sdca_interrupt_info *irq_info, struct snd_soc_component_driver *component_drv, struct snd_soc_dai_driver **dai_drv, int *num_dai_drv, const struct snd_soc_dai_ops *ops); diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h index e580201d69d5de..8a180fe123a999 100644 --- a/include/sound/sdca_function.h +++ b/include/sound/sdca_function.h @@ -9,6 +9,7 @@ #ifndef __SDCA_FUNCTION_H__ #define __SDCA_FUNCTION_H__ +#include #include #include #include @@ -48,6 +49,22 @@ struct sdca_function_desc; */ #define SDCA_MAX_AFFECTED_COUNT 2048 +/* + * Sanity limits for the DisCo Behavior Set library, can be expanded if + * needed. + */ +#define SDCA_MAX_BEHAVIOR_SET_COUNT 256 +#define SDCA_MAX_BEHAVIOR_COUNT 256 + +/* BitIndex controls use up to 64 Control Number bytes (SDCA 1.0 sec. 7.3.9). */ +#define SDCA_MAX_CONTROL_NUMBERS 64 +#define SDCA_MAX_BMT_BITS (SDCA_MAX_CONTROL_NUMBERS * BITS_PER_BYTE) + +/* Generic BitIndex Mapping Table layout (SDCA 1.0 sec. 7.3.9.2, Fig. 114). */ +#define SDCA_BMT_COLS 3 +#define SDCA_BMT_BIT 0 +#define SDCA_BMT_BEHAVIOR_SET 1 + /** * enum sdca_function_type - SDCA Function Type codes * @SDCA_FUNCTION_TYPE_SMART_AMP: Amplifier with protection features. @@ -787,6 +804,68 @@ struct sdca_control_range { u32 *data; }; +/** + * struct sdca_bmt_bits - selected BitIndex bits and their Control Numbers + * @bits: Bitmap of selected bit positions in a BitIndex Mapping Table. + * @control_numbers: Bitmap of Control Numbers containing selected bits. + * + * A BitIndex control is a set of byte controls, with one or more used bits + * in each control. Each group of 8 bits in @bits corresponds to one byte + * control. A bit is set in @bits if that control register bit is part of + * the BMT. + * (SDCA 1.0 Table 178, note 6: each Control Number holds eight BitIndex bits.) + * + * There is one bit in @control_numbers for each control. A bit is set in + * @control_numbers if any of its bits is selected in @bits. This allows + * quick iteration over only the Control Numbers. + */ +struct sdca_bmt_bits { + DECLARE_BITMAP(bits, SDCA_MAX_BMT_BITS); + DECLARE_BITMAP(control_numbers, SDCA_MAX_CONTROL_NUMBERS); +}; + +/** + * sdca_for_each_bmt_control - iterate over Control Numbers selected in a BMT + * @number: Loop variable containing the Control Number. + * @bmt: Pointer to the BMT bitmaps. + */ +#define sdca_for_each_bmt_control(number, bmt) \ + for_each_set_bit(number, (bmt)->control_numbers, SDCA_MAX_CONTROL_NUMBERS) + +/** + * sdca_bmt_control_mask - get the selected bits for one Control Number + * @bmt: BMT bitmaps containing the selected bits. + * @number: Control Number. + * + * Return: An eight-bit mask of the selected bits in the control byte. + */ +static inline u8 sdca_bmt_control_mask(const struct sdca_bmt_bits *bmt, unsigned int number) +{ + return bitmap_get_value8(bmt->bits, number * BITS_PER_BYTE); +} + +/** + * struct sdca_cbns - a list of Class Behavior Numbers + * @cbns: Array of Class Behavior Numbers. + * @num_cbns: Number of entries in @cbns. + */ +struct sdca_cbns { + const u32 *cbns; + int num_cbns; +}; + +/** + * struct sdca_behavior_set - one entry in the DisCo Behavior Set library + * @id: Behavior Set identifier. + * @cbns: Class Behavior Numbers in this set. + * @num_cbns: Number of entries in @cbns. + */ +struct sdca_behavior_set { + u32 id; + u32 *cbns; + int num_cbns; +}; + /** * struct sdca_control - information for one SDCA Control * @label: Name for the Control, from SDCA Specification v1.0, section 7.1.7. @@ -1160,6 +1239,15 @@ struct sdca_entity_xu { enum sdca_xu_reset_mechanism reset_mechanism; }; +/** + * struct sdca_entity_smpu - information specific to SMPU Entities + * @trigger_ready_max_delay: Host Trigger_Ready polling timeout in microseconds, + * clamped to at least 10 if described, or 0 if not described. + */ +struct sdca_entity_smpu { + unsigned int trigger_ready_max_delay; +}; + /** * struct sdca_entity - information for one SDCA Entity * @label: String such as "OT 12". @@ -1177,6 +1265,7 @@ struct sdca_entity_xu { * @ge: Group Entity specific Entity properties. * @hide: HIDE Entity specific Entity properties. * @xu: XU Entity specific Entity properties. + * @smpu: SMPU Entity specific Entity properties. */ struct sdca_entity { const char *label; @@ -1195,6 +1284,7 @@ struct sdca_entity { struct sdca_entity_ge ge; struct sdca_entity_hide hide; struct sdca_entity_xu xu; + struct sdca_entity_smpu smpu; }; }; @@ -1413,9 +1503,11 @@ struct sdca_function_hid { * @init_table: Pointer to a table of initialization writes. * @entities: Dynamically allocated array of Entities. * @clusters: Dynamically allocated array of Channel Clusters. + * @behavior_sets: Dynamically allocated array of Behavior Sets. * @num_init_table: Number of initialization writes. * @num_entities: Number of Entities reported in this Function. * @num_clusters: Number of Channel Clusters reported in this Function. + * @num_behavior_sets: Number of Behavior Sets reported in this Function. * @busy_max_delay: Maximum Function busy delay in microseconds, before an * error should be reported. * @reset_max_delay: Maximum Function reset delay in microseconds, before an @@ -1429,9 +1521,11 @@ struct sdca_function_data { struct sdca_init_write *init_table; struct sdca_entity *entities; struct sdca_cluster *clusters; + struct sdca_behavior_set *behavior_sets; int num_init_table; int num_entities; int num_clusters; + int num_behavior_sets; unsigned int busy_max_delay; unsigned int reset_max_delay; @@ -1443,7 +1537,7 @@ struct sdca_function_data { }; }; -static inline u32 sdca_range(struct sdca_control_range *range, +static inline u32 sdca_range(const struct sdca_control_range *range, unsigned int col, unsigned int row) { return range->data[(row * range->cols) + col]; @@ -1467,10 +1561,10 @@ int sdca_parse_function(struct device *dev, struct sdca_function_data *function) const char *sdca_find_terminal_name(enum sdca_terminal_type type); struct sdca_control *sdca_selector_find_control(struct device *dev, - struct sdca_entity *entity, + const struct sdca_entity *entity, const int sel); struct sdca_control_range *sdca_control_find_range(struct device *dev, - struct sdca_entity *entity, + const struct sdca_entity *entity, struct sdca_control *control, int cols, int rows); struct sdca_control_range *sdca_selector_find_range(struct device *dev, @@ -1479,7 +1573,14 @@ struct sdca_control_range *sdca_selector_find_range(struct device *dev, struct sdca_cluster *sdca_id_find_cluster(struct device *dev, struct sdca_function_data *function, const int id); -struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, - const char *entity_label); +struct sdca_entity *sdca_find_entity_by_label(const struct sdca_function_data *function, + const char *entity_label); +struct sdca_behavior_set *sdca_find_behavior_set(const struct sdca_function_data *function, + unsigned int id); +bool sdca_behavior_set_contains_cbn(const struct sdca_behavior_set *set, + const struct sdca_cbns *cbns); +int sdca_lookup_bmt_bits(struct device *dev, const struct sdca_function_data *function, + const struct sdca_entity *entity, struct sdca_control *control, + const struct sdca_cbns *cbns, struct sdca_bmt_bits *bmt); #endif diff --git a/include/sound/sdca_wov.h b/include/sound/sdca_wov.h new file mode 100644 index 00000000000000..e597d95891c1db --- /dev/null +++ b/include/sound/sdca_wov.h @@ -0,0 +1,91 @@ +/* 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) 2026 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef __SDCA_WOV_H__ +#define __SDCA_WOV_H__ + +#include +#include + +#define SDCA_WOV_SMPU_LABEL "SMPU 16" +#define SDCA_WOV_MAX_DRAIN_PASSES 32 +#define SDCA_WOV_READY_POLL_US 100 + +/* SDCA v1.0 Table 103: algorithms required for proper operation. */ +#define SDCA_ALGORITHM_CBN_REQUIRED 300 + +/* SDCA v1.0 Table 124: Voice and Speech trigger behaviors. */ +#define SDCA_TRIGGER_CBN_VOICE 2000 +#define SDCA_TRIGGER_CBN_SPEECH 3000 + +struct device; +struct sdca_entity; +struct sdca_function_data; +struct sdca_interrupt; +struct sdca_interrupt_info; +struct snd_soc_component_driver; +struct snd_soc_dai_driver; +struct snd_soc_dapm_route; +struct snd_soc_pcm_stream; + +int sdca_wov_alloc_state(struct sdca_interrupt *interrupt); +void sdca_wov_free_state(struct sdca_interrupt *interrupt); + +int sdca_wov_process_voice_trigger(struct sdca_interrupt *interrupt); + +#if IS_ENABLED(CONFIG_SND_SOC_SDCA_IRQ) +bool sdca_wov_is_terminal(struct device *dev, struct sdca_function_data *function, + struct sdca_entity *entity); + +int sdca_wov_populate_dapm(struct device *dev, struct sdca_entity *entity, + struct snd_soc_dapm_route **route); + +int sdca_wov_populate_dai(struct device *dev, + struct sdca_function_data *function, + struct snd_soc_dai_driver *dai, + const struct snd_soc_pcm_stream *stream, + int entity_index); + +int sdca_wov_init_component(struct device *dev, + struct sdca_function_data *function, + struct sdca_interrupt_info *info, + struct snd_soc_component_driver *component_drv); +#else +static inline bool sdca_wov_is_terminal(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity) +{ + return false; +} + +static inline int sdca_wov_populate_dapm(struct device *dev, struct sdca_entity *entity, + struct snd_soc_dapm_route **route) +{ + return 0; +} + +static inline int sdca_wov_populate_dai(struct device *dev, + struct sdca_function_data *function, + struct snd_soc_dai_driver *dai, + const struct snd_soc_pcm_stream *stream, + int entity_index) +{ + return 0; +} + +static inline int sdca_wov_init_component(struct device *dev, + struct sdca_function_data *function, + struct sdca_interrupt_info *info, + struct snd_soc_component_driver *component_drv) +{ + return 0; +} +#endif + +#endif /* __SDCA_WOV_H__ */ diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig index c371323cc36e2b..7c4f6a2d95dd65 100644 --- a/sound/soc/sdca/Kconfig +++ b/sound/soc/sdca/Kconfig @@ -23,6 +23,7 @@ config SND_SOC_SDCA_IRQ bool "SDCA IRQ support" select REGMAP select REGMAP_IRQ + select SND_SOC_COMPRESS depends on SND_SOC_SDCA default y help diff --git a/sound/soc/sdca/Makefile b/sound/soc/sdca/Makefile index b3b0f5d94c8de8..0ab916d351c6f2 100644 --- a/sound/soc/sdca/Makefile +++ b/sound/soc/sdca/Makefile @@ -3,7 +3,8 @@ 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 sdca_jack.o +snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_IRQ) += sdca_interrupts.o sdca_jack.o \ + sdca_wov.o snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_FDL) += sdca_fdl.o snd-soc-sdca-class-y := sdca_class.o diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index ccc579f878ff1e..b21972ccfbeca8 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -113,6 +114,10 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun *num_routes += !!entity->iot.is_dataport; *num_controls += !entity->iot.is_dataport; *num_dais += !!entity->iot.is_dataport; + if (sdca_wov_is_terminal(dev, function, entity)) { + *num_dais += 1; + *num_routes += 1; + } break; case SDCA_ENTITY_TYPE_PDE: *num_routes += entity->pde.num_managed; @@ -336,7 +341,7 @@ static int entity_parse_ot(struct device *dev, struct snd_soc_dapm_widget **widget, struct snd_soc_dapm_route **route) { - int i; + int i, ret; if (entity->iot.is_dataport) { const char *aif_name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", @@ -347,6 +352,12 @@ static int entity_parse_ot(struct device *dev, (*widget)->id = snd_soc_dapm_aif_out; add_route(route, aif_name, NULL, entity->label); + + if (sdca_wov_is_terminal(dev, function, entity)) { + ret = sdca_wov_populate_dapm(dev, entity, route); + if (ret) + return ret; + } } else { (*widget)->id = snd_soc_dapm_spk; } @@ -1301,7 +1312,8 @@ EXPORT_SYMBOL_NS(sdca_asoc_populate_rate_format, "SND_SOC_SDCA"); * @ops: DAI ops to be attached to each of the created DAI drivers. * * This function populates an array of ASoC DAI drivers from the DisCo - * information for a particular SDCA Function. Typically, + * information for a particular SDCA Function, including compressed capture + * DAIs for supported WoV terminals. Typically, * snd_soc_asoc_count_component will be used to allocate an * appropriately sized array before calling this function. * @@ -1352,6 +1364,13 @@ int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *funct dais[j].name = entity->label; dais[j].ops = ops; j++; + + if (sdca_wov_is_terminal(dev, function, entity)) { + ret = sdca_wov_populate_dai(dev, function, &dais[j], stream, i); + if (ret) + return ret; + j++; + } } return 0; @@ -1362,6 +1381,7 @@ EXPORT_SYMBOL_NS(sdca_asoc_populate_dais, "SND_SOC_SDCA"); * sdca_asoc_populate_component - fill in a component driver for a Function * @dev: Pointer to the device against which allocations will be done. * @function: Pointer to the Function information. + * @irq_info: SDCA interrupt information used by the WoV callbacks. * @component_drv: Pointer to the component driver to be populated. * @dai_drv: Pointer to the DAI driver array to be allocated and populated. * @num_dai_drv: Pointer to integer that will be populated with the number of @@ -1376,6 +1396,7 @@ EXPORT_SYMBOL_NS(sdca_asoc_populate_dais, "SND_SOC_SDCA"); */ int sdca_asoc_populate_component(struct device *dev, struct sdca_function_data *function, + struct sdca_interrupt_info *irq_info, struct snd_soc_component_driver *component_drv, struct snd_soc_dai_driver **dai_drv, int *num_dai_drv, const struct snd_soc_dai_ops *ops) @@ -1420,6 +1441,10 @@ int sdca_asoc_populate_component(struct device *dev, if (ret) return ret; + ret = sdca_wov_init_component(dev, function, irq_info, component_drv); + if (ret) + return ret; + component_drv->dapm_widgets = widgets; component_drv->num_dapm_widgets = num_widgets; component_drv->dapm_routes = routes; diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 13b70f0f0dbf7e..09bfcba5fed2fd 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -324,6 +324,13 @@ static int class_function_boot(struct class_function_drv *drv) return 0; } +static void class_function_irq_cleanup_late(void *data) +{ + struct class_function_drv *drv = data; + + sdca_irq_cleanup_late(drv->dev, drv->function, drv->core->irq_info); +} + static int class_function_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *aux_dev_id) { @@ -415,7 +422,7 @@ static int class_function_probe(struct auxiliary_device *auxdev, break; } - ret = sdca_asoc_populate_component(dev, drv->function, cmp_drv, + ret = sdca_asoc_populate_component(dev, drv->function, core->irq_info, cmp_drv, &dais, &num_dais, &class_function_sdw_ops); if (ret) @@ -432,6 +439,14 @@ static int class_function_probe(struct auxiliary_device *auxdev, if (ret) goto err_pm; + /* + * Register this before the component: devres will remove its ALSA + * interfaces before freeing the interrupt state referenced by them. + */ + ret = devm_add_action_or_reset(dev, class_function_irq_cleanup_late, drv); + if (ret) + return ret; + ret = class_function_boot(drv); if (ret) goto err_pm; @@ -453,13 +468,6 @@ static int class_function_probe(struct auxiliary_device *auxdev, return ret; } -static void class_function_remove(struct auxiliary_device *auxdev) -{ - struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); - - sdca_irq_cleanup_late(drv->dev, drv->function, drv->core->irq_info); -} - static int class_function_runtime_suspend(struct device *dev) { struct auxiliary_device *auxdev = to_auxiliary_dev(dev); @@ -616,7 +624,6 @@ static struct auxiliary_driver class_function_drv = { }, .probe = class_function_probe, - .remove = class_function_remove, .id_table = class_function_id_table }; module_auxiliary_driver(class_function_drv); diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index 1eb6418e132dcb..adff219535317a 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,8 @@ */ #define SDCA_PROPERTY_LENGTH 64 +#define SDCA_SMPU_TRIGGER_READY_MIN_DELAY_US 10 + #if IS_ENABLED(CONFIG_ACPI) static int patch_sdca_function_type(u32 interface_revision, u32 *function_type) { @@ -804,6 +807,7 @@ static bool find_sdca_control_volatile(const struct sdca_entity *entity, } switch (SDCA_CTL_TYPE(entity->type, control->sel)) { + case SDCA_CTL_TYPE_S(SMPU, TRIGGER_ENABLE): case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): case SDCA_CTL_TYPE_S(XU, FDL_MESSAGEOFFSET): case SDCA_CTL_TYPE_S(XU, FDL_MESSAGELENGTH): @@ -1460,6 +1464,29 @@ static int find_sdca_entity_xu(struct device *dev, return 0; } +static int find_sdca_entity_smpu(struct device *dev, + struct fwnode_handle *entity_node, + struct sdca_entity *entity) +{ + struct sdca_entity_smpu *smpu = &entity->smpu; + u32 delay; + int ret; + + ret = fwnode_property_read_u32(entity_node, + "mipi-sdca-smpu-trigger-ready-max-delay", + &delay); + if (!ret) + smpu->trigger_ready_max_delay = + max(delay, SDCA_SMPU_TRIGGER_READY_MIN_DELAY_US); + else if (ret != -EINVAL) { + dev_err(dev, "%pfwP: failed to read trigger ready delay: %d\n", + entity_node, ret); + return ret; + } + + return 0; +} + static int find_sdca_entity(struct device *dev, struct sdca_function_data *function, struct fwnode_handle *function_node, struct fwnode_handle *entity_node, @@ -1514,6 +1541,9 @@ static int find_sdca_entity(struct device *dev, struct sdca_function_data *funct case SDCA_ENTITY_TYPE_HIDE: ret = find_sdca_entity_hide(dev, entity_node, entity); break; + case SDCA_ENTITY_TYPE_SMPU: + ret = find_sdca_entity_smpu(dev, entity_node, entity); + break; default: break; } @@ -1601,8 +1631,202 @@ static int find_sdca_entities(struct device *dev, struct fwnode_handle *function return 0; } -struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, - const char *entity_label) +struct sdca_behavior_set *sdca_find_behavior_set(const struct sdca_function_data *function, + unsigned int id) +{ + int i; + + for (i = 0; i < function->num_behavior_sets; i++) { + if (function->behavior_sets[i].id == id) + return &function->behavior_sets[i]; + } + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(sdca_find_behavior_set, "SND_SOC_SDCA"); + +/** + * sdca_behavior_set_contains_cbn - test whether a behavior set contains any requested CBN + * @set: Behavior set to search. + * @cbns: Class Behavior Numbers to match, with their entry count. + * + * Return: True if any CBN in @set matches an entry in @cbns, otherwise false. + */ +bool sdca_behavior_set_contains_cbn(const struct sdca_behavior_set *set, + const struct sdca_cbns *cbns) +{ + int i, j; + + for (i = 0; i < set->num_cbns; i++) { + for (j = 0; j < cbns->num_cbns; j++) { + if (set->cbns[i] == cbns->cbns[j]) + return true; + } + } + + return false; +} +EXPORT_SYMBOL_NS_GPL(sdca_behavior_set_contains_cbn, "SND_SOC_SDCA"); + +/** + * sdca_lookup_bmt_bits - find BitIndex bits matching Class Behavior Numbers + * @dev: pointer to struct device. + * @function: Function containing the DisCo Behavior Set library. + * @entity: Entity containing @control. + * @control: Control whose range contains the BitIndex Mapping Table. + * @cbns: Class Behavior Numbers to match, with their entry count. + * @bmt: Matching BitIndex bits and their Control Numbers. + * + * Matches are added to @bmt's bitmaps, which the caller must initialize. + * The bitmaps may be partially updated on error. + * + * Return: Zero on success, or -EINVAL if the BMT or its behavior sets are invalid. + */ +int sdca_lookup_bmt_bits(struct device *dev, const struct sdca_function_data *function, + const struct sdca_entity *entity, struct sdca_control *control, + const struct sdca_cbns *cbns, struct sdca_bmt_bits *bmt) +{ + const struct sdca_control_range *range; + int i; + + range = sdca_control_find_range(dev, entity, control, SDCA_BMT_COLS, 0); + if (!range || !range->rows) { + dev_err(dev, "%s: invalid BMT for control %#x\n", entity->label, control->sel); + return -EINVAL; + } + + /* SDCA 1.0 sec. 7.3.9.2, Fig. 114: BMT rows map bit numbers to behavior sets. */ + for (i = 0; i < range->rows; i++) { + const struct sdca_behavior_set *set; + unsigned int bit, set_id; + + bit = sdca_range(range, SDCA_BMT_BIT, i); + set_id = sdca_range(range, SDCA_BMT_BEHAVIOR_SET, i); + dev_dbg(dev, "%s: BMT row %d bit %u set %#x\n", entity->label, i, bit, set_id); + if (bit >= SDCA_MAX_BMT_BITS) + return -EINVAL; + + set = sdca_find_behavior_set(function, set_id); + if (!set || !set->cbns || set->num_cbns <= 0) { + dev_err(dev, "%s: invalid BMT behavior set %#x\n", entity->label, set_id); + return -EINVAL; + } + + if (sdca_behavior_set_contains_cbn(set, cbns)) { + __set_bit(bit, bmt->bits); + __set_bit(bit / BITS_PER_BYTE, bmt->control_numbers); + } + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_lookup_bmt_bits, "SND_SOC_SDCA"); + +static int find_sdca_behavior_numbers(struct device *dev, + struct fwnode_handle *behavior_node, + const char *property, u32 **numbers) +{ + int count; + int ret; + + count = fwnode_property_count_u32(behavior_node, property); + if (count < 0) + return count; + if (count > SDCA_MAX_BEHAVIOR_COUNT) + return -EINVAL; + if (!count) + return 0; + + *numbers = devm_kcalloc(dev, count, sizeof(**numbers), GFP_KERNEL); + if (!*numbers) + return -ENOMEM; + + ret = fwnode_property_read_u32_array(behavior_node, property, + *numbers, count); + if (ret) + return ret; + + return count; +} + +static int find_sdca_behavior_sets(struct device *dev, + struct fwnode_handle *function_node, + struct sdca_function_data *function) +{ + const char *id_list = "mipi-sdca-behavior-set-id-list"; + const char *cbn_list = "mipi-sdca-behavior-set-cbn-list"; + struct sdca_behavior_set *sets; + int count; + int i; + int ret; + + count = fwnode_property_count_u32(function_node, id_list); + if (count == -EINVAL || !count) + return 0; + if (count < 0) + return count; + if (count > SDCA_MAX_BEHAVIOR_SET_COUNT) + return -EINVAL; + + u32 *ids __free(kfree) = kcalloc(count, sizeof(*ids), GFP_KERNEL); + if (!ids) + return -ENOMEM; + + ret = fwnode_property_read_u32_array(function_node, id_list, ids, count); + if (ret) + return ret; + + sets = devm_kcalloc(dev, count, sizeof(*sets), GFP_KERNEL); + if (!sets) + return -ENOMEM; + + for (i = 0; i < count; i++) { + struct fwnode_handle *behavior_node; + char property[SDCA_PROPERTY_LENGTH]; + int j; + + for (j = 0; j < i; j++) { + if (ids[j] != ids[i]) + continue; + + dev_err(dev, "%pfwP: duplicate behavior set ID %#x\n", + function_node, ids[i]); + return -EINVAL; + } + + sets[i].id = ids[i]; + snprintf(property, sizeof(property), + "mipi-sdca-behavior-set-id-0x%X-subproperties", ids[i]); + + behavior_node = fwnode_get_named_child_node(function_node, property); + if (!behavior_node) { + dev_err(dev, "%pfwP: behavior set node %s not found\n", + function_node, property); + return -EINVAL; + } + + ret = find_sdca_behavior_numbers(dev, behavior_node, cbn_list, + &sets[i].cbns); + if (ret < 0) { + dev_err(dev, "%pfwP: invalid CBN list: %d\n", + behavior_node, ret); + } else { + sets[i].num_cbns = ret; + } + + fwnode_handle_put(behavior_node); + if (ret < 0) + return ret; + } + + function->behavior_sets = sets; + function->num_behavior_sets = count; + + return 0; +} + +struct sdca_entity *sdca_find_entity_by_label(const struct sdca_function_data *function, + const char *entity_label) { struct sdca_entity *entity = NULL; char tmp[64]; @@ -2240,6 +2464,10 @@ int sdca_parse_function(struct device *dev, struct sdca_function_data *function) if (ret) return ret; + ret = find_sdca_behavior_sets(dev, node, function); + if (ret) + return ret; + ret = find_sdca_connections(dev, node, function); if (ret) return ret; @@ -2312,7 +2540,7 @@ const char *sdca_find_terminal_name(enum sdca_terminal_type type) 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 struct sdca_entity *entity, const int sel) { int i; @@ -2330,7 +2558,7 @@ struct sdca_control *sdca_selector_find_control(struct device *dev, EXPORT_SYMBOL_NS(sdca_selector_find_control, "SND_SOC_SDCA"); struct sdca_control_range *sdca_control_find_range(struct device *dev, - struct sdca_entity *entity, + const struct sdca_entity *entity, struct sdca_control *control, int cols, int rows) { diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 7aebc721a847f5..bb8be7b80da699 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -183,6 +184,19 @@ static irqreturn_t detected_mode_handler(int irq, void *data) return irqret; } +static irqreturn_t wov_trigger_handler(int irq, void *data) +{ + struct sdca_interrupt *interrupt = data; + struct device *dev = interrupt->dev; + int ret; + + ret = sdca_wov_process_voice_trigger(interrupt); + if (ret) + dev_err_ratelimited(dev, "failed to process wake trigger: %d\n", ret); + + return IRQ_HANDLED; +} + static irqreturn_t hid_handler(int irq, void *data) { struct sdca_interrupt *interrupt = data; @@ -466,6 +480,19 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, if (ret) return ret; break; + case SDCA_CTL_TYPE_S(SMPU, TRIGGER_STATUS): + ret = sdca_wov_alloc_state(interrupt); + switch (ret) { + case 0: + interrupt->handler = wov_trigger_handler; + break; + case -ENODEV: + interrupt->handler = base_handler; + break; + default: + return ret; + } + break; case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): interrupt->handler = fdl_owner_handler; interrupt->free_priv = sdca_fdl_free_state; diff --git a/sound/soc/sdca/sdca_wov.c b/sound/soc/sdca/sdca_wov.c new file mode 100644 index 00000000000000..b45ddf7a182ecb --- /dev/null +++ b/sound/soc/sdca/sdca_wov.c @@ -0,0 +1,994 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2026 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TODO: This is a temporary definition for the dummy compressed stream data */ +#define SDCA_WOV_FRAGMENT_SIZE 256 + +static const u32 sdca_wov_trigger_cbn_list[] = { + SDCA_TRIGGER_CBN_VOICE, + SDCA_TRIGGER_CBN_SPEECH, +}; + +static const struct sdca_cbns sdca_wov_trigger_cbns = { + .cbns = sdca_wov_trigger_cbn_list, + .num_cbns = ARRAY_SIZE(sdca_wov_trigger_cbn_list), +}; + +struct sdca_wov_state { + struct mutex lock; + struct sdca_interrupt *interrupt; + struct sdca_entity *mfpu; + struct sdca_control *algorithm_enable; + struct sdca_control *algorithm_ready; + struct sdca_control *enable; + struct sdca_control *status; + struct sdca_control *ready; + struct snd_compr_stream *stream; + + /* Trigger_Enable BMT bits mapped to voice or speech behaviors. */ + struct sdca_bmt_bits voice_trigger_bmt; + /* Algorithm_Ready BMT bits marked MFPU-Required. */ + struct sdca_bmt_bits algorithm_bmt; + + /* True while an arm request is eligible for its first trigger notification. */ + bool armed; + + /* TODO: This is for the dummy compressed stream data */ + size_t produced_bytes; +}; + +struct sdca_wov_cn_status { + u8 status[SDCA_MAX_CONTROL_NUMBERS]; +}; + +struct sdca_wov_compr { + struct snd_compress_ops ops; + struct sdca_interrupt_info *irq_info; +}; + +static void sdca_wov_compr_voice_event(struct sdca_wov_state *state); + +static int sdca_wov_ready(struct sdca_interrupt *interrupt) +{ + const struct sdca_wov_state *state = interrupt->priv; + unsigned int number, reg, mask, ready; + int ret; + + dev_dbg(interrupt->dev, "wov: ready mode %#x\n", state->ready->mode); + + /* SDCA 1.0 sec. 6.3.7.10: each selected trigger must be ready before enabling. */ + sdca_for_each_bmt_control(number, &state->voice_trigger_bmt) { + mask = sdca_bmt_control_mask(&state->voice_trigger_bmt, number); + reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, + state->ready->sel, number); + ret = regmap_read(interrupt->function_regmap, reg, &ready); + if (ret) + return ret; + + dev_dbg(interrupt->dev, "wov: Trigger_Ready CN %u ready %#x mask %#x\n", + number, ready, mask); + + if ((ready & mask) != mask) + return -EAGAIN; + } + + return 0; +} + +static int sdca_wov_wait_ready(struct sdca_interrupt *interrupt) +{ + const struct sdca_wov_state *state = interrupt->priv; + unsigned int timeout = interrupt->entity->smpu.trigger_ready_max_delay; + unsigned int poll_us; + int poll_ret; + int ready_ret; + + dev_dbg(interrupt->dev, "wov: Wait for ready: mode %#x timeout %u us\n", + state->ready->mode, timeout); + + /* SDCA 1.0 sec. 7.9.17 permits DC Trigger_Ready; a constant needs only one check. */ + if (state->ready->mode == SDCA_ACCESS_MODE_DC) + return sdca_wov_ready(interrupt); + + if (!timeout) + return -EINVAL; + + poll_us = min_t(unsigned int, timeout, SDCA_WOV_READY_POLL_US); + poll_ret = read_poll_timeout(sdca_wov_ready, ready_ret, + ready_ret != -EAGAIN, + poll_us, timeout, false, + interrupt); + if (poll_ret || ready_ret) { + dev_warn(interrupt->dev, + "wov: Trigger_Ready not ready (poll_ret:%d ready_ret:%d)\n", + poll_ret, ready_ret); + } + + if (poll_ret) + return poll_ret; + + return ready_ret; +} + +static int sdca_wov_read_status(struct sdca_interrupt *interrupt, + struct sdca_wov_cn_status *status, bool *pending) +{ + const struct sdca_wov_state *state = interrupt->priv; + unsigned int number; + int ret; + + dev_vdbg(interrupt->dev, "wov: Read selector %#x\n", state->status->sel); + + memset(status, 0, sizeof(*status)); + *pending = false; + + /* SDCA 1.0 sec. 6.3.7.11.1, example 1, step 4: snapshot Trigger_Status. */ + sdca_for_each_bmt_control(number, &state->voice_trigger_bmt) { + unsigned int mask = sdca_bmt_control_mask(&state->voice_trigger_bmt, number); + unsigned int reg, value; + + reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, + state->status->sel, number); + ret = regmap_read(interrupt->function_regmap, reg, &value); + if (ret) + return ret; + + dev_vdbg(interrupt->dev, "wov: CN %u status %#x mask %#x\n", + number, value, mask); + + status->status[number] = value & mask; + if (status->status[number]) + *pending = true; + } + + return 0; +} + +static int sdca_wov_clear_status(struct sdca_interrupt *interrupt, + const struct sdca_wov_cn_status *status) +{ + const struct sdca_wov_state *state = interrupt->priv; + unsigned int number; + unsigned int reg; + int ret; + + dev_vdbg(interrupt->dev, "wov: Write selector %#x\n", state->status->sel); + + /* SDCA 1.0 sec. 6.3.7.11.1, example 1, step 6: clear the observed status bits. */ + sdca_for_each_bmt_control(number, &state->voice_trigger_bmt) { + if (!status->status[number]) + continue; + + dev_dbg(interrupt->dev, "wov: CN %u clear %#x\n", number, status->status[number]); + + reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, + state->status->sel, number); + + ret = regmap_write(interrupt->function_regmap, reg, status->status[number]); + if (ret) + return ret; + } + + return 0; +} + +/* + * SDCA 1.0 sec. 6.3.7.11, Fig. 150: a repeated event between the status read and + * RW1C write keeps its status bit set. Drain pending status so stale events + * cannot raise an interrupt when triggers are enabled again. + */ +static int sdca_wov_drain_status(struct sdca_interrupt *interrupt) +{ + struct sdca_wov_cn_status status; + unsigned int pass; + bool pending; + int ret; + + dev_dbg(interrupt->dev, "wov: Drain status (IRQ %d)\n", interrupt->irq); + + for (pass = 0; pass < SDCA_WOV_MAX_DRAIN_PASSES; pass++) { + ret = sdca_wov_read_status(interrupt, &status, &pending); + if (ret || !pending) + return ret; + + ret = sdca_wov_clear_status(interrupt, &status); + if (ret) + return ret; + } + + return -EAGAIN; +} + +static void sdca_wov_clear_device_interrupt(struct sdca_interrupt *interrupt) +{ + unsigned int interrupt_position = interrupt->control->interrupt_position; + unsigned int reg = SDW_SCP_SDCA_INT1 + (interrupt_position / BITS_PER_BYTE); + unsigned int mask = BIT(interrupt_position % BITS_PER_BYTE); + int ret; + + dev_vdbg(interrupt->dev, "wov: Clear IRQ position %d (reg %#x mask %#x)\n", + interrupt_position, reg, mask); + + ret = regmap_write(interrupt->device_regmap, reg, mask); + if (ret) { + dev_warn(interrupt->dev, "wov: Clear IRQ position %d failed: %d\n", + interrupt_position, ret); + } +} + +static int sdca_wov_update_enable(struct sdca_interrupt *interrupt, bool enable) +{ + const struct sdca_wov_state *state = interrupt->priv; + unsigned int number, reg; + int first_ret = 0; + int ret; + + dev_dbg(interrupt->dev, "wov: Update enable=%d\n", enable); + + /* + * SDCA 1.0 sec. 7.9.18: Trigger_Enable uses the trigger BMT's BitIndex mapping. + * Bits may span multiple Control Number bytes (Table 178, note 6), so update + * every byte containing voice/speech bits while preserving unrelated bits. + */ + sdca_for_each_bmt_control(number, &state->voice_trigger_bmt) { + unsigned int mask = sdca_bmt_control_mask(&state->voice_trigger_bmt, number); + + reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, + state->enable->sel, number); + ret = regmap_assign_bits(interrupt->function_regmap, reg, mask, enable); + + /* Keep going on error if disabling. */ + if (ret && !first_ret) + first_ret = ret; + if (ret && enable) + break; + } + + return first_ret; +} + +static int sdca_wov_update_algorithm_enable(struct sdca_interrupt *interrupt, bool enable) +{ + const struct sdca_function_data *function = interrupt->function; + const struct sdca_wov_state *state = interrupt->priv; + const struct sdca_entity *mfpu = state->mfpu; + unsigned int number, reg, mask; + int first_ret = 0; + int ret; + + /* SDCA 1.0 sec. 7.9.7.1: Algorithm_Enable shares Algorithm_Ready's BMT. */ + sdca_for_each_bmt_control(number, &state->algorithm_bmt) { + mask = sdca_bmt_control_mask(&state->algorithm_bmt, number); + reg = SDW_SDCA_CTL(function->desc->adr, mfpu->id, + state->algorithm_enable->sel, number); + ret = regmap_assign_bits(interrupt->function_regmap, reg, mask, enable); + if (ret) { + dev_err(interrupt->dev, + "wov: %s: failed to write Algorithm_Enable CN %u: %d\n", + mfpu->label, number, ret); + if (!first_ret) + first_ret = ret; + /* Attempt all control bytes when disabling. */ + if (enable) + break; + } + } + + return first_ret; +} + +static int sdca_wov_set_algorithm_enable(struct sdca_interrupt *interrupt, bool enable) +{ + const struct sdca_function_data *function = interrupt->function; + const struct sdca_wov_state *state = interrupt->priv; + const struct sdca_entity *mfpu = state->mfpu; + unsigned int number, reg, mask, value; + int ret, restore_ret; + + if (!enable) + return sdca_wov_update_algorithm_enable(interrupt, false); + + /* SDCA 1.0 sec. 6.3.3.7, rule 4: verify all selected algorithms before enabling. */ + sdca_for_each_bmt_control(number, &state->algorithm_bmt) { + mask = sdca_bmt_control_mask(&state->algorithm_bmt, number); + reg = SDW_SDCA_CTL(function->desc->adr, mfpu->id, + state->algorithm_ready->sel, number); + ret = regmap_read(interrupt->function_regmap, reg, &value); + if (ret) { + dev_err(interrupt->dev, + "wov: %s: failed to read Algorithm_Ready CN %u: %d\n", + mfpu->label, number, ret); + return ret; + } + + dev_dbg(interrupt->dev, "wov: %s Algorithm_Ready CN %u value %#x mask %#x\n", + mfpu->label, number, value, mask); + + if ((value & mask) != mask) { + dev_err(interrupt->dev, + "wov: %s: required algorithms not ready (CN %u value %#x mask %#x)\n", + mfpu->label, number, value, mask); + /* Algorithm preparation must complete before arming the stream. */ + return -EIO; + } + } + + ret = sdca_wov_update_algorithm_enable(interrupt, true); + if (ret) { + /* A later control byte may have failed after earlier algorithms were enabled. */ + restore_ret = sdca_wov_update_algorithm_enable(interrupt, false); + if (restore_ret) + dev_err(interrupt->dev, "wov: failed to disable MFPU algorithms: %d\n", + restore_ret); + return ret; + } + + dev_dbg(interrupt->dev, "wov: %s required algorithms enabled\n", mfpu->label); + + return 0; +} + +static int sdca_wov_enable_voice_trigger(struct sdca_interrupt *interrupt) +{ + struct sdca_wov_state *state = interrupt->priv; + int restore_ret; + int ret; + + dev_dbg(interrupt->dev, "wov: armed=%d\n", state->armed); + + guard(mutex)(&state->lock); + + /* Already armed; leave trigger status intact in case an IRQ is pending. */ + if (state->armed) + return 0; + + /* Keep triggers disabled while enabling the algorithm and clearing stale status. */ + ret = sdca_wov_update_enable(interrupt, false); + if (ret) { + dev_err(interrupt->dev, + "wov: %s: failed to disable triggers before arming: %d\n", + interrupt->entity->label, ret); + return ret; + } + + /* Keep the upstream MFPU processing active while the SMPU listens for a trigger. */ + ret = sdca_wov_set_algorithm_enable(interrupt, true); + if (ret) + return ret; + + /* Discard stale events from before this arm request. */ + ret = sdca_wov_drain_status(interrupt); + if (ret) { + dev_err(interrupt->dev, + "wov: %s: failed to drain trigger status before arming: %d\n", + interrupt->entity->label, ret); + return ret; + } + + sdca_wov_clear_device_interrupt(interrupt); + + /* SDCA 1.0 sec. 6.3.7.10: wait for Trigger_Ready before setting Trigger_Enable. */ + ret = sdca_wov_wait_ready(interrupt); + if (ret) { + dev_err(interrupt->dev, "wov: %s: Trigger_Ready wait failed: %d\n", + interrupt->entity->label, ret); + return ret; + } + + /* SDCA 1.0 sec. 7.9.18: enable the triggers. */ + ret = sdca_wov_update_enable(interrupt, true); + if (ret) { + dev_err(interrupt->dev, "wov: %s: failed to enable voice triggers: %d\n", + interrupt->entity->label, ret); + + /* Roll back enables. */ + restore_ret = sdca_wov_update_enable(interrupt, false); + if (restore_ret) + dev_err(interrupt->dev, + "wov: failed to disable WoV triggers: %d\n", + restore_ret); + + return ret; + } + + state->armed = true; + dev_dbg(interrupt->dev, "wov: %s: wake-on-voice trigger started\n", + interrupt->entity->label); + + return 0; +} + +static int sdca_wov_cancel_voice_trigger(struct sdca_interrupt *interrupt) +{ + struct sdca_wov_state *state = interrupt->priv; + int ret; + + dev_dbg(interrupt->dev, "wov: Cancel voice trigger (armed=%d)\n", state->armed); + + guard(mutex)(&state->lock); + + state->armed = false; + + ret = sdca_wov_update_enable(interrupt, false); + if (ret) + return ret; + + dev_dbg(interrupt->dev, "wov: %s: trigger stopped\n", interrupt->entity->label); + + /* Discard events from the canceled request. */ + ret = sdca_wov_drain_status(interrupt); + if (ret) + return ret; + + sdca_wov_clear_device_interrupt(interrupt); + + return sdca_wov_set_algorithm_enable(interrupt, false); +} + +int sdca_wov_process_voice_trigger(struct sdca_interrupt *interrupt) +{ + struct sdca_wov_state *state = interrupt->priv; + struct sdca_wov_cn_status status; + bool notify = false; + bool pending; + int ret; + + if (!state) + return -EINVAL; + + WARN_ON_ONCE(!pm_runtime_active(interrupt->dev)); + + guard(mutex)(&state->lock); + + dev_dbg(interrupt->dev, "wov: IRQ %d Process trigger (armed=%d)\n", + interrupt->irq, state->armed); + + /* SDCA 1.0 sec. 6.3.7.11.1, example 1, step 4: read the detected trigger bits. */ + ret = sdca_wov_read_status(interrupt, &status, &pending); + if (ret) + goto err; + + if (pending) { + /* Deliver at most one notification per arm, even if the interrupt repeats. */ + notify = state->armed; + state->armed = false; + + /* One event completes the request; disable triggers until the next explicit arm. */ + ret = sdca_wov_update_enable(interrupt, false); + if (ret) + goto err; + + dev_dbg(interrupt->dev, "wov: %s: trigger stopped\n", + interrupt->entity->label); + + ret = sdca_wov_clear_status(interrupt, &status); + if (ret) + goto err; + } + +err: + /* Attempt IRQ acknowledgment even on errors to avoid an interrupt storm. */ + sdca_wov_clear_device_interrupt(interrupt); + + /* Report the observed event even if subsequent hardware cleanup failed. */ + if (notify) + sdca_wov_compr_voice_event(state); + + return ret; +} + +static bool sdca_wov_is_supported_interrupt(struct sdca_interrupt *interrupt) +{ + const struct sdca_function_data *function; + u32 type; + + function = interrupt->function; + type = function->desc->type; + + dev_vdbg(interrupt->dev, "wov: function %#x type %#x entity %s\n", + function->desc->adr, type, interrupt->entity->label); + + switch (type) { + case SDCA_FUNCTION_TYPE_SMART_MIC: + if (sdca_find_entity_by_label(function, SDCA_WOV_SMPU_LABEL) == interrupt->entity) + return true; + + return false; + default: + return false; + } +} + +static bool sdca_behavior_set_is_wov(struct device *dev, + const struct sdca_behavior_set *set) +{ + if (sdca_behavior_set_contains_cbn(set, &sdca_wov_trigger_cbns)) { + dev_dbg(dev, "wov: behavior set %#x is WoV\n", set->id); + return true; + } + + dev_vdbg(dev, "wov: behavior set %#x (CBN count %d) not WoV\n", set->id, set->num_cbns); + + return false; +} + +static int sdca_wov_check_bmt_controls(const struct sdca_interrupt *interrupt, + const struct sdca_entity *entity, + const struct sdca_bmt_bits *bmt, u64 cn_list) +{ + unsigned int number; + + sdca_for_each_bmt_control(number, bmt) { + if (!(cn_list & BIT_ULL(number))) { + dev_err(interrupt->dev, + "wov: %s: BMT CN %u has incomplete controls\n", + entity->label, number); + return -EINVAL; + } + } + + return 0; +} + +static int sdca_wov_lookup_algorithm_bmt_bits(struct sdca_interrupt *interrupt, + struct sdca_wov_state *state) +{ + static const u32 cbn_list[] = { SDCA_ALGORITHM_CBN_REQUIRED }; + static const struct sdca_cbns cbns = { + .cbns = cbn_list, + .num_cbns = ARRAY_SIZE(cbn_list), + }; + int ret; + + /* SDCA 1.0 sec. 7.9.7.1: the algorithm BMT is in Algorithm_Ready's range. */ + /* Select MFPU-Required algorithms (SDCA 1.0 Table 103) for WoV. */ + ret = sdca_lookup_bmt_bits(interrupt->dev, interrupt->function, + state->mfpu, state->algorithm_ready, &cbns, + &state->algorithm_bmt); + if (ret) + return ret; + + return sdca_wov_check_bmt_controls(interrupt, state->mfpu, &state->algorithm_bmt, + state->algorithm_ready->cn_list & + state->algorithm_enable->cn_list); +} + +static int sdca_wov_lookup_trigger_bmt_bits(struct sdca_interrupt *interrupt, + struct sdca_wov_state *state) +{ + int ret; + + /* SDCA 1.0 sec. 6.3.7.7: Trigger_Ready, Trigger_Enable and Trigger_Status share a BMT. */ + /* Select Voice/Speech triggers (SDCA 1.0 sec. 6.3.7.7.1, Table 124). */ + ret = sdca_lookup_bmt_bits(interrupt->dev, interrupt->function, + interrupt->entity, state->enable, + &sdca_wov_trigger_cbns, &state->voice_trigger_bmt); + if (ret) + return ret; + + if (bitmap_empty(state->voice_trigger_bmt.bits, SDCA_MAX_BMT_BITS)) { + dev_err(interrupt->dev, "wov: No Voice/Speech triggers defined\n"); + return -ENODEV; + } + + return sdca_wov_check_bmt_controls(interrupt, interrupt->entity, + &state->voice_trigger_bmt, + state->enable->cn_list & state->ready->cn_list & + state->status->cn_list); +} + +/** + * sdca_wov_alloc_state - allocate wake-on-voice state for an SMPU interrupt + * @interrupt: Trigger_Status interrupt and its SDCA context. + * + * The standard SMPU 16 role is selected by its DisCo Entity Label. Voice and + * Speech trigger bits are then derived from the Trigger_Enable BitIndex + * Mapping Table and the Function's DisCo Behavior Set library. Required MFPU 16 + * algorithm bits are derived from its Algorithm_Ready BitIndex Mapping Table. + * + * Return: Zero on success, -ENODEV if this is not the standard WoV trigger + * interrupt, or another negative error code. + */ +int sdca_wov_alloc_state(struct sdca_interrupt *interrupt) +{ + const struct sdca_entity *smpu; + int ret; + + dev_dbg(interrupt->dev, "wov: %s\n", interrupt->entity->label); + + if (!sdca_wov_is_supported_interrupt(interrupt)) + return -ENODEV; + + /* SDCA 1.0 sec. 7.9.19: Trigger_Status must support RW1C acknowledgment. */ + smpu = interrupt->entity; + if (interrupt->control->mode != SDCA_ACCESS_MODE_RW1C) { + dev_err(interrupt->dev, + "wov: %s: Trigger_Status access mode %#x is not RW1C\n", + smpu->label, interrupt->control->mode); + return -EINVAL; + } + + if (interrupt->control->interrupt_position < 0 || + interrupt->control->interrupt_position >= SDCA_MAX_INTERRUPTS) + return -EINVAL; + + struct sdca_wov_state *state __free(kfree) = kzalloc_obj(*state); + if (!state) + return -ENOMEM; + + state->interrupt = interrupt; + state->status = interrupt->control; + state->enable = sdca_selector_find_control(interrupt->dev, smpu, + SDCA_CTL_SMPU_TRIGGER_ENABLE); + state->ready = sdca_selector_find_control(interrupt->dev, smpu, + SDCA_CTL_SMPU_TRIGGER_READY); + + if (!state->enable || !state->ready) { + dev_err(interrupt->dev, "wov: %s: missing trigger controls\n", smpu->label); + return -EINVAL; + } + + if (state->enable->mode != SDCA_ACCESS_MODE_RW) { + dev_err(interrupt->dev, "wov: %s: Trigger_Enable access mode %#x is not RW\n", + smpu->label, state->enable->mode); + return -EINVAL; + } + + switch (state->ready->mode) { + case SDCA_ACCESS_MODE_DC: + break; + default: + if (!smpu->smpu.trigger_ready_max_delay) { + dev_err(interrupt->dev, "wov: %s: trigger ready delay missing\n", + smpu->label); + return -EINVAL; + } + break; + } + + state->mfpu = sdca_find_entity_by_label(interrupt->function, "MFPU 16"); + if (!state->mfpu || state->mfpu->type != SDCA_ENTITY_TYPE_MFPU) { + dev_err(interrupt->dev, "%s: MFPU 16 not found\n", smpu->label); + return -ENODEV; + } + + state->algorithm_enable = sdca_selector_find_control(interrupt->dev, state->mfpu, + SDCA_CTL_MFPU_ALGORITHM_ENABLE); + state->algorithm_ready = sdca_selector_find_control(interrupt->dev, state->mfpu, + SDCA_CTL_MFPU_ALGORITHM_READY); + if (!state->algorithm_enable || !state->algorithm_ready) + return -EINVAL; + + ret = sdca_wov_lookup_algorithm_bmt_bits(interrupt, state); + if (ret) + return ret; + + ret = sdca_wov_lookup_trigger_bmt_bits(interrupt, state); + if (ret) + return ret; + + mutex_init(&state->lock); + interrupt->priv = no_free_ptr(state); + interrupt->free_priv = sdca_wov_free_state; + + return 0; +} + +static struct sdca_wov_state *sdca_wov_get_state(const struct sdca_interrupt *interrupt) +{ + struct sdca_wov_state *state; + + if (interrupt->free_priv != sdca_wov_free_state) + return NULL; + + state = interrupt->priv; + if (!state || state->interrupt != interrupt) + return NULL; + + return state; +} + +static int sdca_wov_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + const struct sdca_wov_compr *compr = + container_of_const(component->driver->compress_ops, struct sdca_wov_compr, ops); + struct sdca_interrupt_info *info = compr->irq_info; + int i; + + if (stream->direction != SND_COMPRESS_CAPTURE) + return -EINVAL; + + guard(mutex)(&info->irq_lock); + + for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { + const struct sdca_interrupt *interrupt = &info->irqs[i]; + struct sdca_wov_state *state; + + if (interrupt->component != component || interrupt->irq <= 0) + continue; + + state = sdca_wov_get_state(interrupt); + if (!state) + continue; + + guard(mutex)(&state->lock); + if (state->stream) + return -EBUSY; + + state->produced_bytes = 0; + state->stream = stream; + stream->runtime->private_data = state; + return 0; + } + + return -ENODEV; +} + +static int sdca_wov_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct sdca_wov_state *state = stream->runtime->private_data; + + state->stream = NULL; + stream->runtime->private_data = NULL; + + /* Cancel the outstanding arm before ASoC can power down the stream's DAPM path. */ + return sdca_wov_cancel_voice_trigger(state->interrupt); +} + +static int sdca_wov_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *stream, int cmd) +{ + struct sdca_wov_state *state = stream->runtime->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + return sdca_wov_enable_voice_trigger(state->interrupt); + case SNDRV_PCM_TRIGGER_STOP: + ret = sdca_wov_cancel_voice_trigger(state->interrupt); + if (!ret) + state->produced_bytes = 0; + return ret; + default: + return -EINVAL; + } +} + +static int sdca_wov_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_tstamp64 *tstamp) +{ + struct sdca_wov_state *state = stream->runtime->private_data; + + /* TODO: This is Dummy implementation for test only */ + + guard(mutex)(&state->lock); + + memset(tstamp, 0, sizeof(*tstamp)); + tstamp->copied_total = state->produced_bytes; + dev_dbg(state->interrupt->dev, + "wov: compr pointer: produced %zu transferred %llu fragment %u state %d\n", + state->produced_bytes, stream->runtime->total_bytes_transferred, + stream->runtime->fragment_size, stream->runtime->state); + + return 0; +} + +static int sdca_wov_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *stream, char __user *buf, + size_t count) +{ + const struct sdca_wov_state *state = stream->runtime->private_data; + char data[SDCA_WOV_FRAGMENT_SIZE]; + + /* TODO: This is Dummy implementation for test only, produces a buffer of '.' */ + count = min_t(size_t, count, SDCA_WOV_FRAGMENT_SIZE); + memset(data, '.', count); + if (copy_to_user(buf, data, count)) + return -EFAULT; + + dev_dbg(state->interrupt->dev, "wov: compr: copied %zu bytes\n", count); + + return count; +} + +static int sdca_wov_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_caps *caps) +{ + memset(caps, 0, sizeof(*caps)); + caps->direction = SND_COMPRESS_CAPTURE; + caps->num_codecs = 1; + caps->codecs[0] = SND_AUDIOCODEC_BESPOKE; + + /* TODO: Provide real fragment size and max_fragments */ + caps->min_fragment_size = SDCA_WOV_FRAGMENT_SIZE; + caps->max_fragment_size = SDCA_WOV_FRAGMENT_SIZE; + caps->min_fragments = 1; + caps->max_fragments = 1; + + return 0; +} + +static void sdca_wov_compr_voice_event(struct sdca_wov_state *state) +{ + lockdep_assert_held(&state->lock); + + if (!state->stream) { + dev_dbg(state->interrupt->dev, "wov: voice event without a compressed stream\n"); + return; + } + + dev_dbg(state->interrupt->dev, "wov: compr: voice event\n"); + + /* TODO: This is only a dummy fragment for testing */ + state->produced_bytes += SDCA_WOV_FRAGMENT_SIZE; + + /* Wake readers after the dummy fragment is visible through the pointer callback. */ + snd_compr_fragment_elapsed(state->stream); +} + +static const struct snd_compress_ops sdca_wov_compress_ops = { + .open = sdca_wov_compr_open, + .free = sdca_wov_compr_free, + .trigger = sdca_wov_compr_trigger, + .pointer = sdca_wov_compr_pointer, + .copy = sdca_wov_compr_copy, + .get_caps = sdca_wov_compr_get_caps, +}; + +int sdca_wov_populate_dapm(struct device *dev, struct sdca_entity *entity, + struct snd_soc_dapm_route **route) +{ + (*route)->sink = devm_kasprintf(dev, GFP_KERNEL, "%s WoV Capture", entity->label); + if (!(*route)->sink) + return -ENOMEM; + + (*route)->control = NULL; + (*route)->source = entity->label; + (*route)++; + + return 0; +} + +int sdca_wov_populate_dai(struct device *dev, + struct sdca_function_data *function, + struct snd_soc_dai_driver *dai, + const struct snd_soc_pcm_stream *stream, + int entity_index) +{ + const struct sdca_entity *entity = &function->entities[entity_index]; + + dai->name = devm_kasprintf(dev, GFP_KERNEL, "%s WoV", entity->label); + if (!dai->name) + return -ENOMEM; + + dai->id = function->num_entities + entity_index; + dai->capture = *stream; + dai->capture.channels_max = 1; + + /* sdca_wov_populate_dapm() routes this stream to the DisCo terminal. */ + dai->capture.stream_name = devm_kasprintf(dev, GFP_KERNEL, "%s Capture", dai->name); + if (!dai->capture.stream_name) + return -ENOMEM; + + return 0; +} + +/* Match a data-port OT fed by the SMPU supported by the WoV callbacks. */ +bool sdca_wov_is_terminal(struct device *dev, struct sdca_function_data *function, + struct sdca_entity *entity) +{ + const struct sdca_control_range *range; + const struct sdca_entity *smpu; + int i; + + if (function->desc->type != SDCA_FUNCTION_TYPE_SMART_MIC || + entity->type != SDCA_ENTITY_TYPE_OT || !entity->iot.is_dataport) + return false; + + smpu = sdca_find_entity_by_label(function, SDCA_WOV_SMPU_LABEL); + if (!smpu || smpu->type != SDCA_ENTITY_TYPE_SMPU) + return false; + + for (i = 0; i < entity->num_sources; i++) { + if (entity->sources[i] == smpu) + break; + } + if (i == entity->num_sources) + return false; + + for (i = 0; i < smpu->num_controls; i++) { + if (smpu->controls[i].sel == SDCA_CTL_SMPU_TRIGGER_ENABLE) + break; + } + if (i == smpu->num_controls) + return false; + + range = sdca_control_find_range(dev, smpu, &smpu->controls[i], + SDCA_BMT_COLS, 0); + if (!range) + return false; + + for (i = 0; i < range->rows; i++) { + const struct sdca_behavior_set *set; + unsigned int set_id = sdca_range(range, SDCA_BMT_BEHAVIOR_SET, i); + + set = sdca_find_behavior_set(function, set_id); + if (set && set->cbns && sdca_behavior_set_is_wov(dev, set)) + return true; + } + + return false; +} + +int sdca_wov_init_component(struct device *dev, + struct sdca_function_data *function, + struct sdca_interrupt_info *info, + struct snd_soc_component_driver *component_drv) +{ + struct sdca_wov_compr *compr; + int i; + + for (i = 0; i < function->num_entities - 1; i++) { + if (sdca_wov_is_terminal(dev, function, &function->entities[i])) + break; + } + if (i == function->num_entities - 1) + return 0; + + compr = devm_kzalloc(dev, sizeof(*compr), GFP_KERNEL); + if (!compr) + return -ENOMEM; + + compr->ops = sdca_wov_compress_ops; + compr->irq_info = info; + component_drv->compress_ops = &compr->ops; + + return 0; +} + +void sdca_wov_free_state(struct sdca_interrupt *interrupt) +{ + struct sdca_wov_state *state; + + state = sdca_wov_get_state(interrupt); + if (!state) + return; + + interrupt->priv = NULL; + interrupt->free_priv = NULL; + mutex_destroy(&state->lock); + kfree(state); +}