171f7990aSMaciej Strozek // SPDX-License-Identifier: GPL-2.0 271f7990aSMaciej Strozek // Copyright (C) 2025 Cirrus Logic, Inc. and 371f7990aSMaciej Strozek // Cirrus Logic International Semiconductor Ltd. 471f7990aSMaciej Strozek 571f7990aSMaciej Strozek /* 671f7990aSMaciej Strozek * The MIPI SDCA specification is available for public downloads at 771f7990aSMaciej Strozek * https://www.mipi.org/mipi-sdca-v1-0-download 871f7990aSMaciej Strozek */ 971f7990aSMaciej Strozek 1071f7990aSMaciej Strozek #include <linux/acpi.h> 1171f7990aSMaciej Strozek #include <linux/device.h> 1271f7990aSMaciej Strozek #include <linux/dev_printk.h> 1371f7990aSMaciej Strozek #include <linux/dmi.h> 1471f7990aSMaciej Strozek #include <linux/firmware.h> 1571f7990aSMaciej Strozek #include <linux/module.h> 1671f7990aSMaciej Strozek #include <linux/pci.h> 170723affaSCharles Keepax #include <linux/pm_runtime.h> 1871f7990aSMaciej Strozek #include <linux/regmap.h> 1971f7990aSMaciej Strozek #include <linux/sprintf.h> 2071f7990aSMaciej Strozek #include <linux/soundwire/sdw.h> 2171f7990aSMaciej Strozek #include <linux/soundwire/sdw_registers.h> 2271f7990aSMaciej Strozek #include <sound/sdca.h> 2371f7990aSMaciej Strozek #include <sound/sdca_fdl.h> 2471f7990aSMaciej Strozek #include <sound/sdca_function.h> 2571f7990aSMaciej Strozek #include <sound/sdca_interrupts.h> 2671f7990aSMaciej Strozek #include <sound/sdca_ump.h> 2771f7990aSMaciej Strozek 2871f7990aSMaciej Strozek /** 2971f7990aSMaciej Strozek * sdca_reset_function - send an SDCA function reset 3071f7990aSMaciej Strozek * @dev: Device pointer for error messages. 3171f7990aSMaciej Strozek * @function: Pointer to the SDCA Function. 3271f7990aSMaciej Strozek * @regmap: Pointer to the SDCA Function regmap. 3371f7990aSMaciej Strozek * 3471f7990aSMaciej Strozek * Return: Zero on success or a negative error code. 3571f7990aSMaciej Strozek */ 3671f7990aSMaciej Strozek int sdca_reset_function(struct device *dev, struct sdca_function_data *function, 3771f7990aSMaciej Strozek struct regmap *regmap) 3871f7990aSMaciej Strozek { 3971f7990aSMaciej Strozek unsigned int reg = SDW_SDCA_CTL(function->desc->adr, 4071f7990aSMaciej Strozek SDCA_ENTITY_TYPE_ENTITY_0, 4171f7990aSMaciej Strozek SDCA_CTL_ENTITY_0_FUNCTION_ACTION, 0); 4271f7990aSMaciej Strozek unsigned int val, poll_us; 4371f7990aSMaciej Strozek int ret; 4471f7990aSMaciej Strozek 4571f7990aSMaciej Strozek ret = regmap_write(regmap, reg, SDCA_CTL_ENTITY_0_RESET_FUNCTION_NOW); 4671f7990aSMaciej Strozek if (ret) // Allowed for function reset to not be implemented 4771f7990aSMaciej Strozek return 0; 4871f7990aSMaciej Strozek 4971f7990aSMaciej Strozek if (!function->reset_max_delay) { 5071f7990aSMaciej Strozek dev_err(dev, "No reset delay specified in DisCo\n"); 5171f7990aSMaciej Strozek return -EINVAL; 5271f7990aSMaciej Strozek } 5371f7990aSMaciej Strozek 5471f7990aSMaciej Strozek poll_us = umin(function->reset_max_delay >> 4, 1000); 5571f7990aSMaciej Strozek 5671f7990aSMaciej Strozek ret = regmap_read_poll_timeout(regmap, reg, val, !val, poll_us, 5771f7990aSMaciej Strozek function->reset_max_delay); 5871f7990aSMaciej Strozek if (ret) { 5971f7990aSMaciej Strozek dev_err(dev, "Failed waiting for function reset: %d\n", ret); 6071f7990aSMaciej Strozek return ret; 6171f7990aSMaciej Strozek } 6271f7990aSMaciej Strozek 6371f7990aSMaciej Strozek return 0; 6471f7990aSMaciej Strozek } 6571f7990aSMaciej Strozek EXPORT_SYMBOL_NS(sdca_reset_function, "SND_SOC_SDCA"); 6671f7990aSMaciej Strozek 670723affaSCharles Keepax /** 680723affaSCharles Keepax * sdca_fdl_sync - wait for a function to finish FDL 690723affaSCharles Keepax * @dev: Device pointer for error messages. 700723affaSCharles Keepax * @function: Pointer to the SDCA Function. 710723affaSCharles Keepax * @info: Pointer to the SDCA interrupt info for this device. 720723affaSCharles Keepax * 730723affaSCharles Keepax * Return: Zero on success or a negative error code. 740723affaSCharles Keepax */ 750723affaSCharles Keepax int sdca_fdl_sync(struct device *dev, struct sdca_function_data *function, 760723affaSCharles Keepax struct sdca_interrupt_info *info) 770723affaSCharles Keepax { 780723affaSCharles Keepax static const int fdl_retries = 6; 790723affaSCharles Keepax unsigned long begin_timeout = msecs_to_jiffies(100); 800723affaSCharles Keepax unsigned long done_timeout = msecs_to_jiffies(4000); 810723affaSCharles Keepax int nfdl; 820723affaSCharles Keepax int i, j; 830723affaSCharles Keepax 840723affaSCharles Keepax for (i = 0; i < fdl_retries; i++) { 850723affaSCharles Keepax nfdl = 0; 860723affaSCharles Keepax 870723affaSCharles Keepax for (j = 0; j < SDCA_MAX_INTERRUPTS; j++) { 880723affaSCharles Keepax struct sdca_interrupt *interrupt = &info->irqs[j]; 890723affaSCharles Keepax struct fdl_state *fdl_state; 900723affaSCharles Keepax unsigned long time; 910723affaSCharles Keepax 920723affaSCharles Keepax if (interrupt->function != function || 930723affaSCharles Keepax !interrupt->entity || !interrupt->control || 940723affaSCharles Keepax interrupt->entity->type != SDCA_ENTITY_TYPE_XU || 950723affaSCharles Keepax interrupt->control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER) 960723affaSCharles Keepax continue; 970723affaSCharles Keepax 980723affaSCharles Keepax fdl_state = interrupt->priv; 990723affaSCharles Keepax nfdl++; 1000723affaSCharles Keepax 1010723affaSCharles Keepax /* 1020723affaSCharles Keepax * Looking for timeout without any new FDL requests 1030723affaSCharles Keepax * to imply the device has completed initial 1040723affaSCharles Keepax * firmware setup. Alas the specification doesn't 1050723affaSCharles Keepax * have any mechanism to detect this. 1060723affaSCharles Keepax */ 1070723affaSCharles Keepax time = wait_for_completion_timeout(&fdl_state->begin, 1080723affaSCharles Keepax begin_timeout); 1090723affaSCharles Keepax if (!time) { 1100723affaSCharles Keepax dev_dbg(dev, "no new FDL starts\n"); 1110723affaSCharles Keepax nfdl--; 1120723affaSCharles Keepax continue; 1130723affaSCharles Keepax } 1140723affaSCharles Keepax 1150723affaSCharles Keepax time = wait_for_completion_timeout(&fdl_state->done, 1160723affaSCharles Keepax done_timeout); 1170723affaSCharles Keepax if (!time) { 1180723affaSCharles Keepax dev_err(dev, "timed out waiting for FDL to complete\n"); 119e92e25f7SCharles Keepax goto error; 1200723affaSCharles Keepax } 1210723affaSCharles Keepax } 1220723affaSCharles Keepax 1230723affaSCharles Keepax if (!nfdl) 1240723affaSCharles Keepax return 0; 1250723affaSCharles Keepax } 1260723affaSCharles Keepax 1270723affaSCharles Keepax dev_err(dev, "too many FDL requests\n"); 128e92e25f7SCharles Keepax 129e92e25f7SCharles Keepax error: 130e92e25f7SCharles Keepax for (j = 0; j < SDCA_MAX_INTERRUPTS; j++) { 131e92e25f7SCharles Keepax struct sdca_interrupt *interrupt = &info->irqs[j]; 132e92e25f7SCharles Keepax struct fdl_state *fdl_state; 133e92e25f7SCharles Keepax 134e92e25f7SCharles Keepax if (interrupt->function != function || 135e92e25f7SCharles Keepax !interrupt->entity || !interrupt->control || 136e92e25f7SCharles Keepax interrupt->entity->type != SDCA_ENTITY_TYPE_XU || 137e92e25f7SCharles Keepax interrupt->control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER) 138e92e25f7SCharles Keepax continue; 139e92e25f7SCharles Keepax 140e92e25f7SCharles Keepax disable_irq(interrupt->irq); 141e92e25f7SCharles Keepax 142e92e25f7SCharles Keepax fdl_state = interrupt->priv; 143e92e25f7SCharles Keepax 144e92e25f7SCharles Keepax sdca_ump_cancel_timeout(&fdl_state->timeout); 145e92e25f7SCharles Keepax } 146e92e25f7SCharles Keepax 1470723affaSCharles Keepax return -ETIMEDOUT; 1480723affaSCharles Keepax } 1490723affaSCharles Keepax EXPORT_SYMBOL_NS_GPL(sdca_fdl_sync, "SND_SOC_SDCA"); 1500723affaSCharles Keepax 15171f7990aSMaciej Strozek static char *fdl_get_sku_filename(struct device *dev, 15271f7990aSMaciej Strozek struct sdca_fdl_file *fdl_file) 15371f7990aSMaciej Strozek { 15471f7990aSMaciej Strozek struct device *parent = dev; 15571f7990aSMaciej Strozek const char *product_vendor; 15671f7990aSMaciej Strozek const char *product_sku; 15771f7990aSMaciej Strozek 15871f7990aSMaciej Strozek /* 15971f7990aSMaciej Strozek * Try to find pci_dev manually because the card may not be ready to be 16071f7990aSMaciej Strozek * used for snd_soc_card_get_pci_ssid yet 16171f7990aSMaciej Strozek */ 16271f7990aSMaciej Strozek while (parent) { 16371f7990aSMaciej Strozek if (dev_is_pci(parent)) { 16471f7990aSMaciej Strozek struct pci_dev *pci_dev = to_pci_dev(parent); 16571f7990aSMaciej Strozek 16671f7990aSMaciej Strozek return kasprintf(GFP_KERNEL, "sdca/%x/%x/%x/%x.bin", 16771f7990aSMaciej Strozek fdl_file->vendor_id, 16871f7990aSMaciej Strozek pci_dev->subsystem_vendor, 16971f7990aSMaciej Strozek pci_dev->subsystem_device, 17071f7990aSMaciej Strozek fdl_file->file_id); 17171f7990aSMaciej Strozek } else { 17271f7990aSMaciej Strozek parent = parent->parent; 17371f7990aSMaciej Strozek } 17471f7990aSMaciej Strozek } 17571f7990aSMaciej Strozek 17671f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_SYS_VENDOR); 17771f7990aSMaciej Strozek if (!product_vendor || !strcmp(product_vendor, "Default string")) 17871f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); 17971f7990aSMaciej Strozek if (!product_vendor || !strcmp(product_vendor, "Default string")) 18071f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_CHASSIS_VENDOR); 18171f7990aSMaciej Strozek if (!product_vendor) 18271f7990aSMaciej Strozek product_vendor = "unknown"; 18371f7990aSMaciej Strozek 18471f7990aSMaciej Strozek product_sku = dmi_get_system_info(DMI_PRODUCT_SKU); 18571f7990aSMaciej Strozek if (!product_sku || !strcmp(product_sku, "Default string")) 18671f7990aSMaciej Strozek product_sku = dmi_get_system_info(DMI_PRODUCT_NAME); 18771f7990aSMaciej Strozek if (!product_sku) 18871f7990aSMaciej Strozek product_sku = "unknown"; 18971f7990aSMaciej Strozek 19071f7990aSMaciej Strozek return kasprintf(GFP_KERNEL, "sdca/%x/%s/%s/%x.bin", fdl_file->vendor_id, 19171f7990aSMaciej Strozek product_vendor, product_sku, fdl_file->file_id); 19271f7990aSMaciej Strozek } 19371f7990aSMaciej Strozek 19471f7990aSMaciej Strozek static int fdl_load_file(struct sdca_interrupt *interrupt, 19571f7990aSMaciej Strozek struct sdca_fdl_set *set, int file_index) 19671f7990aSMaciej Strozek { 19771f7990aSMaciej Strozek struct device *dev = interrupt->dev; 19871f7990aSMaciej Strozek struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; 19971f7990aSMaciej Strozek const struct firmware *firmware = NULL; 20071f7990aSMaciej Strozek struct acpi_sw_file *swf = NULL, *tmp; 20171f7990aSMaciej Strozek struct sdca_fdl_file *fdl_file; 20271f7990aSMaciej Strozek char *disk_filename; 20371f7990aSMaciej Strozek int ret; 20471f7990aSMaciej Strozek int i; 20571f7990aSMaciej Strozek 20671f7990aSMaciej Strozek if (!set) { 20771f7990aSMaciej Strozek dev_err(dev, "request to load SWF with no set\n"); 20871f7990aSMaciej Strozek return -EINVAL; 20971f7990aSMaciej Strozek } 21071f7990aSMaciej Strozek 21171f7990aSMaciej Strozek fdl_file = &set->files[file_index]; 21271f7990aSMaciej Strozek 21371f7990aSMaciej Strozek if (fdl_data->swft) { 21471f7990aSMaciej Strozek tmp = fdl_data->swft->files; 21571f7990aSMaciej Strozek for (i = 0; i < fdl_data->swft->header.length; i += tmp->file_length, 21671f7990aSMaciej Strozek tmp = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length)) { 21771f7990aSMaciej Strozek if (tmp->vendor_id == fdl_file->vendor_id && 21871f7990aSMaciej Strozek tmp->file_id == fdl_file->file_id) { 21971f7990aSMaciej Strozek dev_dbg(dev, "located SWF in ACPI: %x-%x-%x\n", 22071f7990aSMaciej Strozek tmp->vendor_id, tmp->file_id, 22171f7990aSMaciej Strozek tmp->file_version); 22271f7990aSMaciej Strozek swf = tmp; 22371f7990aSMaciej Strozek break; 22471f7990aSMaciej Strozek } 22571f7990aSMaciej Strozek } 22671f7990aSMaciej Strozek } 22771f7990aSMaciej Strozek 22871f7990aSMaciej Strozek disk_filename = fdl_get_sku_filename(dev, fdl_file); 22971f7990aSMaciej Strozek if (!disk_filename) 23071f7990aSMaciej Strozek return -ENOMEM; 23171f7990aSMaciej Strozek 23271f7990aSMaciej Strozek dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); 23371f7990aSMaciej Strozek 23471f7990aSMaciej Strozek ret = firmware_request_nowarn(&firmware, disk_filename, dev); 23571f7990aSMaciej Strozek kfree(disk_filename); 23671f7990aSMaciej Strozek if (ret) { 23771f7990aSMaciej Strozek disk_filename = kasprintf(GFP_KERNEL, "sdca/%x/%x.bin", 23871f7990aSMaciej Strozek fdl_file->vendor_id, fdl_file->file_id); 23971f7990aSMaciej Strozek if (!disk_filename) 24071f7990aSMaciej Strozek return -ENOMEM; 24171f7990aSMaciej Strozek 24271f7990aSMaciej Strozek dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); 24371f7990aSMaciej Strozek 24471f7990aSMaciej Strozek ret = firmware_request_nowarn(&firmware, disk_filename, dev); 24571f7990aSMaciej Strozek kfree(disk_filename); 24671f7990aSMaciej Strozek } 24771f7990aSMaciej Strozek 24871f7990aSMaciej Strozek if (!ret) { 24971f7990aSMaciej Strozek tmp = (struct acpi_sw_file *)&firmware->data[0]; 25071f7990aSMaciej Strozek 25171f7990aSMaciej Strozek if (firmware->size < sizeof(*tmp) || 25271f7990aSMaciej Strozek tmp->file_length != firmware->size) { 25371f7990aSMaciej Strozek dev_err(dev, "bad disk SWF size\n"); 25471f7990aSMaciej Strozek } else if (!swf || swf->file_version <= tmp->file_version) { 25571f7990aSMaciej Strozek dev_dbg(dev, "using SWF from disk: %x-%x-%x\n", 25671f7990aSMaciej Strozek tmp->vendor_id, tmp->file_id, tmp->file_version); 25771f7990aSMaciej Strozek swf = tmp; 25871f7990aSMaciej Strozek } 25971f7990aSMaciej Strozek } 26071f7990aSMaciej Strozek 26171f7990aSMaciej Strozek if (!swf) { 26271f7990aSMaciej Strozek dev_err(dev, "failed to locate SWF\n"); 26371f7990aSMaciej Strozek return -ENOENT; 26471f7990aSMaciej Strozek } 26571f7990aSMaciej Strozek 26671f7990aSMaciej Strozek ret = sdca_ump_write_message(dev, interrupt->device_regmap, 26771f7990aSMaciej Strozek interrupt->function_regmap, 26871f7990aSMaciej Strozek interrupt->function, interrupt->entity, 26971f7990aSMaciej Strozek SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, 27071f7990aSMaciej Strozek SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, 27171f7990aSMaciej Strozek swf->file_length - offsetof(struct acpi_sw_file, data)); 27271f7990aSMaciej Strozek release_firmware(firmware); 27371f7990aSMaciej Strozek return ret; 27471f7990aSMaciej Strozek } 27571f7990aSMaciej Strozek 27671f7990aSMaciej Strozek static struct sdca_fdl_set *fdl_get_set(struct sdca_interrupt *interrupt) 27771f7990aSMaciej Strozek { 27871f7990aSMaciej Strozek struct device *dev = interrupt->dev; 27971f7990aSMaciej Strozek struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; 28071f7990aSMaciej Strozek struct sdca_entity *xu = interrupt->entity; 28171f7990aSMaciej Strozek struct sdca_control_range *range; 28271f7990aSMaciej Strozek unsigned int val; 28371f7990aSMaciej Strozek int i, ret; 28471f7990aSMaciej Strozek 28571f7990aSMaciej Strozek ret = regmap_read(interrupt->function_regmap, 28671f7990aSMaciej Strozek SDW_SDCA_CTL(interrupt->function->desc->adr, xu->id, 28771f7990aSMaciej Strozek SDCA_CTL_XU_FDL_SET_INDEX, 0), 28871f7990aSMaciej Strozek &val); 28971f7990aSMaciej Strozek if (ret < 0) { 29071f7990aSMaciej Strozek dev_err(dev, "failed to read FDL set index: %d\n", ret); 29171f7990aSMaciej Strozek return NULL; 29271f7990aSMaciej Strozek } 29371f7990aSMaciej Strozek 29471f7990aSMaciej Strozek range = sdca_selector_find_range(dev, xu, SDCA_CTL_XU_FDL_SET_INDEX, 29571f7990aSMaciej Strozek SDCA_FDL_SET_INDEX_NCOLS, 0); 29671f7990aSMaciej Strozek 29771f7990aSMaciej Strozek val = sdca_range_search(range, SDCA_FDL_SET_INDEX_SET_NUMBER, 29871f7990aSMaciej Strozek val, SDCA_FDL_SET_INDEX_FILE_SET_ID); 29971f7990aSMaciej Strozek 30071f7990aSMaciej Strozek for (i = 0; i < fdl_data->num_sets; i++) { 30171f7990aSMaciej Strozek if (fdl_data->sets[i].id == val) 30271f7990aSMaciej Strozek return &fdl_data->sets[i]; 30371f7990aSMaciej Strozek } 30471f7990aSMaciej Strozek 30571f7990aSMaciej Strozek dev_err(dev, "invalid fileset id: %d\n", val); 30671f7990aSMaciej Strozek return NULL; 30771f7990aSMaciej Strozek } 30871f7990aSMaciej Strozek 30971f7990aSMaciej Strozek static void fdl_end(struct sdca_interrupt *interrupt) 31071f7990aSMaciej Strozek { 31171f7990aSMaciej Strozek struct fdl_state *fdl_state = interrupt->priv; 31271f7990aSMaciej Strozek 31371f7990aSMaciej Strozek if (!fdl_state->set) 31471f7990aSMaciej Strozek return; 31571f7990aSMaciej Strozek 31671f7990aSMaciej Strozek fdl_state->set = NULL; 31771f7990aSMaciej Strozek 3180723affaSCharles Keepax pm_runtime_put(interrupt->dev); 3190723affaSCharles Keepax complete(&fdl_state->done); 3200723affaSCharles Keepax 32171f7990aSMaciej Strozek dev_dbg(interrupt->dev, "completed FDL process\n"); 32271f7990aSMaciej Strozek } 32371f7990aSMaciej Strozek 324e92e25f7SCharles Keepax static void sdca_fdl_timeout_work(struct work_struct *work) 325e92e25f7SCharles Keepax { 326e92e25f7SCharles Keepax struct fdl_state *fdl_state = container_of(work, struct fdl_state, 327e92e25f7SCharles Keepax timeout.work); 328e92e25f7SCharles Keepax struct sdca_interrupt *interrupt = fdl_state->interrupt; 329e92e25f7SCharles Keepax struct device *dev = interrupt->dev; 330e92e25f7SCharles Keepax 331e92e25f7SCharles Keepax dev_err(dev, "FDL transaction timed out\n"); 332e92e25f7SCharles Keepax 333e92e25f7SCharles Keepax guard(mutex)(&fdl_state->lock); 334e92e25f7SCharles Keepax 335e92e25f7SCharles Keepax fdl_end(interrupt); 336e92e25f7SCharles Keepax sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); 337e92e25f7SCharles Keepax } 338e92e25f7SCharles Keepax 33971f7990aSMaciej Strozek static int fdl_status_process(struct sdca_interrupt *interrupt, unsigned int status) 34071f7990aSMaciej Strozek { 34171f7990aSMaciej Strozek struct fdl_state *fdl_state = interrupt->priv; 34271f7990aSMaciej Strozek int ret; 34371f7990aSMaciej Strozek 34471f7990aSMaciej Strozek switch (status) { 34571f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_NEEDS_SET: 34671f7990aSMaciej Strozek dev_dbg(interrupt->dev, "starting FDL process...\n"); 34771f7990aSMaciej Strozek 3480723affaSCharles Keepax pm_runtime_get(interrupt->dev); 3490723affaSCharles Keepax complete(&fdl_state->begin); 3500723affaSCharles Keepax 35171f7990aSMaciej Strozek fdl_state->file_index = 0; 35271f7990aSMaciej Strozek fdl_state->set = fdl_get_set(interrupt); 35371f7990aSMaciej Strozek fallthrough; 35471f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_MORE_FILES_OK: 35571f7990aSMaciej Strozek ret = fdl_load_file(interrupt, fdl_state->set, fdl_state->file_index); 35671f7990aSMaciej Strozek if (ret) { 35771f7990aSMaciej Strozek fdl_end(interrupt); 35871f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_REQ_ABORT; 35971f7990aSMaciej Strozek } 36071f7990aSMaciej Strozek 36171f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_FILE_AVAILABLE; 36271f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_FILE_OK: 36371f7990aSMaciej Strozek if (!fdl_state->set) { 36471f7990aSMaciej Strozek fdl_end(interrupt); 36571f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_REQ_ABORT; 36671f7990aSMaciej Strozek } 36771f7990aSMaciej Strozek 36871f7990aSMaciej Strozek fdl_state->file_index++; 36971f7990aSMaciej Strozek 37071f7990aSMaciej Strozek if (fdl_state->file_index < fdl_state->set->num_files) 37171f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_MORE_FILES; 37271f7990aSMaciej Strozek fallthrough; 37371f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_COMPLETE: 37471f7990aSMaciej Strozek fdl_end(interrupt); 37571f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_COMPLETE; 37671f7990aSMaciej Strozek default: 37771f7990aSMaciej Strozek fdl_end(interrupt); 37871f7990aSMaciej Strozek 37971f7990aSMaciej Strozek if (status & SDCA_CTL_XU_FDLD_REQ_RESET) 38071f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_RESET_ACK; 38171f7990aSMaciej Strozek else if (status & SDCA_CTL_XU_FDLD_REQ_ABORT) 38271f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_COMPLETE; 38371f7990aSMaciej Strozek 38471f7990aSMaciej Strozek dev_err(interrupt->dev, "invalid FDL status: %x\n", status); 38571f7990aSMaciej Strozek return -EINVAL; 38671f7990aSMaciej Strozek } 38771f7990aSMaciej Strozek } 38871f7990aSMaciej Strozek 38971f7990aSMaciej Strozek /** 39071f7990aSMaciej Strozek * sdca_fdl_process - Process the FDL state machine 39171f7990aSMaciej Strozek * @interrupt: SDCA interrupt structure 39271f7990aSMaciej Strozek * 39371f7990aSMaciej Strozek * Based on section 13.2.5 Flow Diagram for File Download, Host side. 39471f7990aSMaciej Strozek * 39571f7990aSMaciej Strozek * Return: Zero on success or a negative error code. 39671f7990aSMaciej Strozek */ 39771f7990aSMaciej Strozek int sdca_fdl_process(struct sdca_interrupt *interrupt) 39871f7990aSMaciej Strozek { 39971f7990aSMaciej Strozek struct device *dev = interrupt->dev; 40071f7990aSMaciej Strozek struct sdca_entity_xu *xu = &interrupt->entity->xu; 401e92e25f7SCharles Keepax struct fdl_state *fdl_state = interrupt->priv; 40271f7990aSMaciej Strozek unsigned int reg, status; 40371f7990aSMaciej Strozek int response, ret; 40471f7990aSMaciej Strozek 40571f7990aSMaciej Strozek ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap, 40671f7990aSMaciej Strozek interrupt->function, interrupt->entity, 40771f7990aSMaciej Strozek interrupt->control); 40871f7990aSMaciej Strozek if (ret) 40971f7990aSMaciej Strozek goto reset_function; 41071f7990aSMaciej Strozek 411e92e25f7SCharles Keepax sdca_ump_cancel_timeout(&fdl_state->timeout); 412e92e25f7SCharles Keepax 413*cc58055bSCharles Keepax scoped_guard(mutex, &fdl_state->lock) { 414*cc58055bSCharles Keepax reg = SDW_SDCA_CTL(interrupt->function->desc->adr, 415*cc58055bSCharles Keepax interrupt->entity->id, SDCA_CTL_XU_FDL_STATUS, 0); 41671f7990aSMaciej Strozek ret = regmap_read(interrupt->function_regmap, reg, &status); 41771f7990aSMaciej Strozek if (ret < 0) { 41871f7990aSMaciej Strozek dev_err(dev, "failed to read FDL status: %d\n", ret); 41971f7990aSMaciej Strozek return ret; 42071f7990aSMaciej Strozek } 42171f7990aSMaciej Strozek 42271f7990aSMaciej Strozek dev_dbg(dev, "FDL status: %#x\n", status); 42371f7990aSMaciej Strozek 42471f7990aSMaciej Strozek ret = fdl_status_process(interrupt, status); 42571f7990aSMaciej Strozek if (ret < 0) 42671f7990aSMaciej Strozek goto reset_function; 42771f7990aSMaciej Strozek 42871f7990aSMaciej Strozek response = ret; 42971f7990aSMaciej Strozek 43071f7990aSMaciej Strozek dev_dbg(dev, "FDL response: %#x\n", response); 43171f7990aSMaciej Strozek 43271f7990aSMaciej Strozek ret = regmap_write(interrupt->function_regmap, reg, 43371f7990aSMaciej Strozek response | (status & ~SDCA_CTL_XU_FDLH_MASK)); 43471f7990aSMaciej Strozek if (ret < 0) { 43571f7990aSMaciej Strozek dev_err(dev, "failed to set FDL status signal: %d\n", ret); 43671f7990aSMaciej Strozek return ret; 43771f7990aSMaciej Strozek } 43871f7990aSMaciej Strozek 43971f7990aSMaciej Strozek ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap, 440*cc58055bSCharles Keepax interrupt->function, 441*cc58055bSCharles Keepax interrupt->entity, 44271f7990aSMaciej Strozek interrupt->control); 44371f7990aSMaciej Strozek if (ret) 44471f7990aSMaciej Strozek return ret; 44571f7990aSMaciej Strozek 44671f7990aSMaciej Strozek switch (response) { 44771f7990aSMaciej Strozek case SDCA_CTL_XU_FDLH_RESET_ACK: 44871f7990aSMaciej Strozek dev_dbg(dev, "FDL request reset\n"); 44971f7990aSMaciej Strozek 45071f7990aSMaciej Strozek switch (xu->reset_mechanism) { 45171f7990aSMaciej Strozek default: 45271f7990aSMaciej Strozek dev_warn(dev, "Requested reset mechanism not implemented\n"); 45371f7990aSMaciej Strozek fallthrough; 45471f7990aSMaciej Strozek case SDCA_XU_RESET_FUNCTION: 45571f7990aSMaciej Strozek goto reset_function; 45671f7990aSMaciej Strozek } 457e92e25f7SCharles Keepax case SDCA_CTL_XU_FDLH_COMPLETE: 458e92e25f7SCharles Keepax if (status & SDCA_CTL_XU_FDLD_REQ_ABORT || 459e92e25f7SCharles Keepax status == SDCA_CTL_XU_FDLD_COMPLETE) 460e92e25f7SCharles Keepax return 0; 461e92e25f7SCharles Keepax fallthrough; 46271f7990aSMaciej Strozek default: 463e92e25f7SCharles Keepax sdca_ump_schedule_timeout(&fdl_state->timeout, xu->max_delay); 46471f7990aSMaciej Strozek return 0; 46571f7990aSMaciej Strozek } 466*cc58055bSCharles Keepax } 46771f7990aSMaciej Strozek 46871f7990aSMaciej Strozek reset_function: 46971f7990aSMaciej Strozek sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); 47071f7990aSMaciej Strozek 47171f7990aSMaciej Strozek return ret; 47271f7990aSMaciej Strozek } 47371f7990aSMaciej Strozek EXPORT_SYMBOL_NS_GPL(sdca_fdl_process, "SND_SOC_SDCA"); 47471f7990aSMaciej Strozek 47571f7990aSMaciej Strozek /** 47671f7990aSMaciej Strozek * sdca_fdl_alloc_state - allocate state for an FDL interrupt 47771f7990aSMaciej Strozek * @interrupt: SDCA interrupt structure. 47871f7990aSMaciej Strozek * 47971f7990aSMaciej Strozek * Return: Zero on success or a negative error code. 48071f7990aSMaciej Strozek */ 48171f7990aSMaciej Strozek int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) 48271f7990aSMaciej Strozek { 48371f7990aSMaciej Strozek struct device *dev = interrupt->dev; 48471f7990aSMaciej Strozek struct fdl_state *fdl_state; 48571f7990aSMaciej Strozek 48671f7990aSMaciej Strozek fdl_state = devm_kzalloc(dev, sizeof(struct fdl_state), GFP_KERNEL); 48771f7990aSMaciej Strozek if (!fdl_state) 48871f7990aSMaciej Strozek return -ENOMEM; 48971f7990aSMaciej Strozek 490e92e25f7SCharles Keepax INIT_DELAYED_WORK(&fdl_state->timeout, sdca_fdl_timeout_work); 4910723affaSCharles Keepax init_completion(&fdl_state->begin); 4920723affaSCharles Keepax init_completion(&fdl_state->done); 493e92e25f7SCharles Keepax mutex_init(&fdl_state->lock); 494e92e25f7SCharles Keepax fdl_state->interrupt = interrupt; 4950723affaSCharles Keepax 49671f7990aSMaciej Strozek interrupt->priv = fdl_state; 49771f7990aSMaciej Strozek 49871f7990aSMaciej Strozek return 0; 49971f7990aSMaciej Strozek } 50071f7990aSMaciej Strozek EXPORT_SYMBOL_NS_GPL(sdca_fdl_alloc_state, "SND_SOC_SDCA"); 501