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 54*336beae2SCharles Keepax /* 55*336beae2SCharles Keepax * Poll up to 16 times but no more than once per ms, these are just 56*336beae2SCharles Keepax * arbitrarily selected values, so may be fine tuned in future. 57*336beae2SCharles Keepax */ 5871f7990aSMaciej Strozek poll_us = umin(function->reset_max_delay >> 4, 1000); 5971f7990aSMaciej Strozek 6071f7990aSMaciej Strozek ret = regmap_read_poll_timeout(regmap, reg, val, !val, poll_us, 6171f7990aSMaciej Strozek function->reset_max_delay); 6271f7990aSMaciej Strozek if (ret) { 6371f7990aSMaciej Strozek dev_err(dev, "Failed waiting for function reset: %d\n", ret); 6471f7990aSMaciej Strozek return ret; 6571f7990aSMaciej Strozek } 6671f7990aSMaciej Strozek 6771f7990aSMaciej Strozek return 0; 6871f7990aSMaciej Strozek } 6971f7990aSMaciej Strozek EXPORT_SYMBOL_NS(sdca_reset_function, "SND_SOC_SDCA"); 7071f7990aSMaciej Strozek 710723affaSCharles Keepax /** 720723affaSCharles Keepax * sdca_fdl_sync - wait for a function to finish FDL 730723affaSCharles Keepax * @dev: Device pointer for error messages. 740723affaSCharles Keepax * @function: Pointer to the SDCA Function. 750723affaSCharles Keepax * @info: Pointer to the SDCA interrupt info for this device. 760723affaSCharles Keepax * 770723affaSCharles Keepax * Return: Zero on success or a negative error code. 780723affaSCharles Keepax */ 790723affaSCharles Keepax int sdca_fdl_sync(struct device *dev, struct sdca_function_data *function, 800723affaSCharles Keepax struct sdca_interrupt_info *info) 810723affaSCharles Keepax { 820723affaSCharles Keepax static const int fdl_retries = 6; 830723affaSCharles Keepax unsigned long begin_timeout = msecs_to_jiffies(100); 840723affaSCharles Keepax unsigned long done_timeout = msecs_to_jiffies(4000); 850723affaSCharles Keepax int nfdl; 860723affaSCharles Keepax int i, j; 870723affaSCharles Keepax 880723affaSCharles Keepax for (i = 0; i < fdl_retries; i++) { 890723affaSCharles Keepax nfdl = 0; 900723affaSCharles Keepax 910723affaSCharles Keepax for (j = 0; j < SDCA_MAX_INTERRUPTS; j++) { 920723affaSCharles Keepax struct sdca_interrupt *interrupt = &info->irqs[j]; 930723affaSCharles Keepax struct fdl_state *fdl_state; 940723affaSCharles Keepax unsigned long time; 950723affaSCharles Keepax 960723affaSCharles Keepax if (interrupt->function != function || 970723affaSCharles Keepax !interrupt->entity || !interrupt->control || 980723affaSCharles Keepax interrupt->entity->type != SDCA_ENTITY_TYPE_XU || 990723affaSCharles Keepax interrupt->control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER) 1000723affaSCharles Keepax continue; 1010723affaSCharles Keepax 1020723affaSCharles Keepax fdl_state = interrupt->priv; 1030723affaSCharles Keepax nfdl++; 1040723affaSCharles Keepax 1050723affaSCharles Keepax /* 1060723affaSCharles Keepax * Looking for timeout without any new FDL requests 1070723affaSCharles Keepax * to imply the device has completed initial 1080723affaSCharles Keepax * firmware setup. Alas the specification doesn't 1090723affaSCharles Keepax * have any mechanism to detect this. 1100723affaSCharles Keepax */ 1110723affaSCharles Keepax time = wait_for_completion_timeout(&fdl_state->begin, 1120723affaSCharles Keepax begin_timeout); 1130723affaSCharles Keepax if (!time) { 1140723affaSCharles Keepax dev_dbg(dev, "no new FDL starts\n"); 1150723affaSCharles Keepax nfdl--; 1160723affaSCharles Keepax continue; 1170723affaSCharles Keepax } 1180723affaSCharles Keepax 1190723affaSCharles Keepax time = wait_for_completion_timeout(&fdl_state->done, 1200723affaSCharles Keepax done_timeout); 1210723affaSCharles Keepax if (!time) { 1220723affaSCharles Keepax dev_err(dev, "timed out waiting for FDL to complete\n"); 123e92e25f7SCharles Keepax goto error; 1240723affaSCharles Keepax } 1250723affaSCharles Keepax } 1260723affaSCharles Keepax 1270723affaSCharles Keepax if (!nfdl) 1280723affaSCharles Keepax return 0; 1290723affaSCharles Keepax } 1300723affaSCharles Keepax 1310723affaSCharles Keepax dev_err(dev, "too many FDL requests\n"); 132e92e25f7SCharles Keepax 133e92e25f7SCharles Keepax error: 134e92e25f7SCharles Keepax for (j = 0; j < SDCA_MAX_INTERRUPTS; j++) { 135e92e25f7SCharles Keepax struct sdca_interrupt *interrupt = &info->irqs[j]; 136e92e25f7SCharles Keepax struct fdl_state *fdl_state; 137e92e25f7SCharles Keepax 138e92e25f7SCharles Keepax if (interrupt->function != function || 139e92e25f7SCharles Keepax !interrupt->entity || !interrupt->control || 140e92e25f7SCharles Keepax interrupt->entity->type != SDCA_ENTITY_TYPE_XU || 141e92e25f7SCharles Keepax interrupt->control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER) 142e92e25f7SCharles Keepax continue; 143e92e25f7SCharles Keepax 144e92e25f7SCharles Keepax disable_irq(interrupt->irq); 145e92e25f7SCharles Keepax 146e92e25f7SCharles Keepax fdl_state = interrupt->priv; 147e92e25f7SCharles Keepax 148e92e25f7SCharles Keepax sdca_ump_cancel_timeout(&fdl_state->timeout); 149e92e25f7SCharles Keepax } 150e92e25f7SCharles Keepax 1510723affaSCharles Keepax return -ETIMEDOUT; 1520723affaSCharles Keepax } 1530723affaSCharles Keepax EXPORT_SYMBOL_NS_GPL(sdca_fdl_sync, "SND_SOC_SDCA"); 1540723affaSCharles Keepax 15571f7990aSMaciej Strozek static char *fdl_get_sku_filename(struct device *dev, 15671f7990aSMaciej Strozek struct sdca_fdl_file *fdl_file) 15771f7990aSMaciej Strozek { 15871f7990aSMaciej Strozek struct device *parent = dev; 15971f7990aSMaciej Strozek const char *product_vendor; 16071f7990aSMaciej Strozek const char *product_sku; 16171f7990aSMaciej Strozek 16271f7990aSMaciej Strozek /* 16371f7990aSMaciej Strozek * Try to find pci_dev manually because the card may not be ready to be 16471f7990aSMaciej Strozek * used for snd_soc_card_get_pci_ssid yet 16571f7990aSMaciej Strozek */ 16671f7990aSMaciej Strozek while (parent) { 16771f7990aSMaciej Strozek if (dev_is_pci(parent)) { 16871f7990aSMaciej Strozek struct pci_dev *pci_dev = to_pci_dev(parent); 16971f7990aSMaciej Strozek 17071f7990aSMaciej Strozek return kasprintf(GFP_KERNEL, "sdca/%x/%x/%x/%x.bin", 17171f7990aSMaciej Strozek fdl_file->vendor_id, 17271f7990aSMaciej Strozek pci_dev->subsystem_vendor, 17371f7990aSMaciej Strozek pci_dev->subsystem_device, 17471f7990aSMaciej Strozek fdl_file->file_id); 17571f7990aSMaciej Strozek } else { 17671f7990aSMaciej Strozek parent = parent->parent; 17771f7990aSMaciej Strozek } 17871f7990aSMaciej Strozek } 17971f7990aSMaciej Strozek 18071f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_SYS_VENDOR); 18171f7990aSMaciej Strozek if (!product_vendor || !strcmp(product_vendor, "Default string")) 18271f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); 18371f7990aSMaciej Strozek if (!product_vendor || !strcmp(product_vendor, "Default string")) 18471f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_CHASSIS_VENDOR); 18571f7990aSMaciej Strozek if (!product_vendor) 18671f7990aSMaciej Strozek product_vendor = "unknown"; 18771f7990aSMaciej Strozek 18871f7990aSMaciej Strozek product_sku = dmi_get_system_info(DMI_PRODUCT_SKU); 18971f7990aSMaciej Strozek if (!product_sku || !strcmp(product_sku, "Default string")) 19071f7990aSMaciej Strozek product_sku = dmi_get_system_info(DMI_PRODUCT_NAME); 19171f7990aSMaciej Strozek if (!product_sku) 19271f7990aSMaciej Strozek product_sku = "unknown"; 19371f7990aSMaciej Strozek 19471f7990aSMaciej Strozek return kasprintf(GFP_KERNEL, "sdca/%x/%s/%s/%x.bin", fdl_file->vendor_id, 19571f7990aSMaciej Strozek product_vendor, product_sku, fdl_file->file_id); 19671f7990aSMaciej Strozek } 19771f7990aSMaciej Strozek 19871f7990aSMaciej Strozek static int fdl_load_file(struct sdca_interrupt *interrupt, 19971f7990aSMaciej Strozek struct sdca_fdl_set *set, int file_index) 20071f7990aSMaciej Strozek { 20171f7990aSMaciej Strozek struct device *dev = interrupt->dev; 20271f7990aSMaciej Strozek struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; 20371f7990aSMaciej Strozek const struct firmware *firmware = NULL; 20471f7990aSMaciej Strozek struct acpi_sw_file *swf = NULL, *tmp; 20571f7990aSMaciej Strozek struct sdca_fdl_file *fdl_file; 20671f7990aSMaciej Strozek char *disk_filename; 20771f7990aSMaciej Strozek int ret; 20871f7990aSMaciej Strozek int i; 20971f7990aSMaciej Strozek 21071f7990aSMaciej Strozek if (!set) { 21171f7990aSMaciej Strozek dev_err(dev, "request to load SWF with no set\n"); 21271f7990aSMaciej Strozek return -EINVAL; 21371f7990aSMaciej Strozek } 21471f7990aSMaciej Strozek 21571f7990aSMaciej Strozek fdl_file = &set->files[file_index]; 21671f7990aSMaciej Strozek 21771f7990aSMaciej Strozek if (fdl_data->swft) { 21871f7990aSMaciej Strozek tmp = fdl_data->swft->files; 21971f7990aSMaciej Strozek for (i = 0; i < fdl_data->swft->header.length; i += tmp->file_length, 22071f7990aSMaciej Strozek tmp = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length)) { 22171f7990aSMaciej Strozek if (tmp->vendor_id == fdl_file->vendor_id && 22271f7990aSMaciej Strozek tmp->file_id == fdl_file->file_id) { 22371f7990aSMaciej Strozek dev_dbg(dev, "located SWF in ACPI: %x-%x-%x\n", 22471f7990aSMaciej Strozek tmp->vendor_id, tmp->file_id, 22571f7990aSMaciej Strozek tmp->file_version); 22671f7990aSMaciej Strozek swf = tmp; 22771f7990aSMaciej Strozek break; 22871f7990aSMaciej Strozek } 22971f7990aSMaciej Strozek } 23071f7990aSMaciej Strozek } 23171f7990aSMaciej Strozek 23271f7990aSMaciej Strozek disk_filename = fdl_get_sku_filename(dev, fdl_file); 23371f7990aSMaciej Strozek if (!disk_filename) 23471f7990aSMaciej Strozek return -ENOMEM; 23571f7990aSMaciej Strozek 23671f7990aSMaciej Strozek dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); 23771f7990aSMaciej Strozek 23871f7990aSMaciej Strozek ret = firmware_request_nowarn(&firmware, disk_filename, dev); 23971f7990aSMaciej Strozek kfree(disk_filename); 24071f7990aSMaciej Strozek if (ret) { 24171f7990aSMaciej Strozek disk_filename = kasprintf(GFP_KERNEL, "sdca/%x/%x.bin", 24271f7990aSMaciej Strozek fdl_file->vendor_id, fdl_file->file_id); 24371f7990aSMaciej Strozek if (!disk_filename) 24471f7990aSMaciej Strozek return -ENOMEM; 24571f7990aSMaciej Strozek 24671f7990aSMaciej Strozek dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); 24771f7990aSMaciej Strozek 24871f7990aSMaciej Strozek ret = firmware_request_nowarn(&firmware, disk_filename, dev); 24971f7990aSMaciej Strozek kfree(disk_filename); 25071f7990aSMaciej Strozek } 25171f7990aSMaciej Strozek 25271f7990aSMaciej Strozek if (!ret) { 25371f7990aSMaciej Strozek tmp = (struct acpi_sw_file *)&firmware->data[0]; 25471f7990aSMaciej Strozek 25571f7990aSMaciej Strozek if (firmware->size < sizeof(*tmp) || 25671f7990aSMaciej Strozek tmp->file_length != firmware->size) { 25771f7990aSMaciej Strozek dev_err(dev, "bad disk SWF size\n"); 25871f7990aSMaciej Strozek } else if (!swf || swf->file_version <= tmp->file_version) { 25971f7990aSMaciej Strozek dev_dbg(dev, "using SWF from disk: %x-%x-%x\n", 26071f7990aSMaciej Strozek tmp->vendor_id, tmp->file_id, tmp->file_version); 26171f7990aSMaciej Strozek swf = tmp; 26271f7990aSMaciej Strozek } 26371f7990aSMaciej Strozek } 26471f7990aSMaciej Strozek 26571f7990aSMaciej Strozek if (!swf) { 26671f7990aSMaciej Strozek dev_err(dev, "failed to locate SWF\n"); 26771f7990aSMaciej Strozek return -ENOENT; 26871f7990aSMaciej Strozek } 26971f7990aSMaciej Strozek 27071f7990aSMaciej Strozek ret = sdca_ump_write_message(dev, interrupt->device_regmap, 27171f7990aSMaciej Strozek interrupt->function_regmap, 27271f7990aSMaciej Strozek interrupt->function, interrupt->entity, 27371f7990aSMaciej Strozek SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, 27471f7990aSMaciej Strozek SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, 27571f7990aSMaciej Strozek swf->file_length - offsetof(struct acpi_sw_file, data)); 27671f7990aSMaciej Strozek release_firmware(firmware); 27771f7990aSMaciej Strozek return ret; 27871f7990aSMaciej Strozek } 27971f7990aSMaciej Strozek 28071f7990aSMaciej Strozek static struct sdca_fdl_set *fdl_get_set(struct sdca_interrupt *interrupt) 28171f7990aSMaciej Strozek { 28271f7990aSMaciej Strozek struct device *dev = interrupt->dev; 28371f7990aSMaciej Strozek struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; 28471f7990aSMaciej Strozek struct sdca_entity *xu = interrupt->entity; 28571f7990aSMaciej Strozek struct sdca_control_range *range; 28671f7990aSMaciej Strozek unsigned int val; 28771f7990aSMaciej Strozek int i, ret; 28871f7990aSMaciej Strozek 28971f7990aSMaciej Strozek ret = regmap_read(interrupt->function_regmap, 29071f7990aSMaciej Strozek SDW_SDCA_CTL(interrupt->function->desc->adr, xu->id, 29171f7990aSMaciej Strozek SDCA_CTL_XU_FDL_SET_INDEX, 0), 29271f7990aSMaciej Strozek &val); 29371f7990aSMaciej Strozek if (ret < 0) { 29471f7990aSMaciej Strozek dev_err(dev, "failed to read FDL set index: %d\n", ret); 29571f7990aSMaciej Strozek return NULL; 29671f7990aSMaciej Strozek } 29771f7990aSMaciej Strozek 29871f7990aSMaciej Strozek range = sdca_selector_find_range(dev, xu, SDCA_CTL_XU_FDL_SET_INDEX, 29971f7990aSMaciej Strozek SDCA_FDL_SET_INDEX_NCOLS, 0); 30071f7990aSMaciej Strozek 30171f7990aSMaciej Strozek val = sdca_range_search(range, SDCA_FDL_SET_INDEX_SET_NUMBER, 30271f7990aSMaciej Strozek val, SDCA_FDL_SET_INDEX_FILE_SET_ID); 30371f7990aSMaciej Strozek 30471f7990aSMaciej Strozek for (i = 0; i < fdl_data->num_sets; i++) { 30571f7990aSMaciej Strozek if (fdl_data->sets[i].id == val) 30671f7990aSMaciej Strozek return &fdl_data->sets[i]; 30771f7990aSMaciej Strozek } 30871f7990aSMaciej Strozek 30971f7990aSMaciej Strozek dev_err(dev, "invalid fileset id: %d\n", val); 31071f7990aSMaciej Strozek return NULL; 31171f7990aSMaciej Strozek } 31271f7990aSMaciej Strozek 31371f7990aSMaciej Strozek static void fdl_end(struct sdca_interrupt *interrupt) 31471f7990aSMaciej Strozek { 31571f7990aSMaciej Strozek struct fdl_state *fdl_state = interrupt->priv; 31671f7990aSMaciej Strozek 31771f7990aSMaciej Strozek if (!fdl_state->set) 31871f7990aSMaciej Strozek return; 31971f7990aSMaciej Strozek 32071f7990aSMaciej Strozek fdl_state->set = NULL; 32171f7990aSMaciej Strozek 3220723affaSCharles Keepax pm_runtime_put(interrupt->dev); 3230723affaSCharles Keepax complete(&fdl_state->done); 3240723affaSCharles Keepax 32571f7990aSMaciej Strozek dev_dbg(interrupt->dev, "completed FDL process\n"); 32671f7990aSMaciej Strozek } 32771f7990aSMaciej Strozek 328e92e25f7SCharles Keepax static void sdca_fdl_timeout_work(struct work_struct *work) 329e92e25f7SCharles Keepax { 330e92e25f7SCharles Keepax struct fdl_state *fdl_state = container_of(work, struct fdl_state, 331e92e25f7SCharles Keepax timeout.work); 332e92e25f7SCharles Keepax struct sdca_interrupt *interrupt = fdl_state->interrupt; 333e92e25f7SCharles Keepax struct device *dev = interrupt->dev; 334e92e25f7SCharles Keepax 335e92e25f7SCharles Keepax dev_err(dev, "FDL transaction timed out\n"); 336e92e25f7SCharles Keepax 337e92e25f7SCharles Keepax guard(mutex)(&fdl_state->lock); 338e92e25f7SCharles Keepax 339e92e25f7SCharles Keepax fdl_end(interrupt); 340e92e25f7SCharles Keepax sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); 341e92e25f7SCharles Keepax } 342e92e25f7SCharles Keepax 34371f7990aSMaciej Strozek static int fdl_status_process(struct sdca_interrupt *interrupt, unsigned int status) 34471f7990aSMaciej Strozek { 34571f7990aSMaciej Strozek struct fdl_state *fdl_state = interrupt->priv; 34671f7990aSMaciej Strozek int ret; 34771f7990aSMaciej Strozek 34871f7990aSMaciej Strozek switch (status) { 34971f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_NEEDS_SET: 35071f7990aSMaciej Strozek dev_dbg(interrupt->dev, "starting FDL process...\n"); 35171f7990aSMaciej Strozek 3520723affaSCharles Keepax pm_runtime_get(interrupt->dev); 3530723affaSCharles Keepax complete(&fdl_state->begin); 3540723affaSCharles Keepax 35571f7990aSMaciej Strozek fdl_state->file_index = 0; 35671f7990aSMaciej Strozek fdl_state->set = fdl_get_set(interrupt); 35771f7990aSMaciej Strozek fallthrough; 35871f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_MORE_FILES_OK: 35971f7990aSMaciej Strozek ret = fdl_load_file(interrupt, fdl_state->set, fdl_state->file_index); 36071f7990aSMaciej Strozek if (ret) { 36171f7990aSMaciej Strozek fdl_end(interrupt); 36271f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_REQ_ABORT; 36371f7990aSMaciej Strozek } 36471f7990aSMaciej Strozek 36571f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_FILE_AVAILABLE; 36671f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_FILE_OK: 36771f7990aSMaciej Strozek if (!fdl_state->set) { 36871f7990aSMaciej Strozek fdl_end(interrupt); 36971f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_REQ_ABORT; 37071f7990aSMaciej Strozek } 37171f7990aSMaciej Strozek 37271f7990aSMaciej Strozek fdl_state->file_index++; 37371f7990aSMaciej Strozek 37471f7990aSMaciej Strozek if (fdl_state->file_index < fdl_state->set->num_files) 37571f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_MORE_FILES; 37671f7990aSMaciej Strozek fallthrough; 37771f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_COMPLETE: 37871f7990aSMaciej Strozek fdl_end(interrupt); 37971f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_COMPLETE; 38071f7990aSMaciej Strozek default: 38171f7990aSMaciej Strozek fdl_end(interrupt); 38271f7990aSMaciej Strozek 38371f7990aSMaciej Strozek if (status & SDCA_CTL_XU_FDLD_REQ_RESET) 38471f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_RESET_ACK; 38571f7990aSMaciej Strozek else if (status & SDCA_CTL_XU_FDLD_REQ_ABORT) 38671f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_COMPLETE; 38771f7990aSMaciej Strozek 38871f7990aSMaciej Strozek dev_err(interrupt->dev, "invalid FDL status: %x\n", status); 38971f7990aSMaciej Strozek return -EINVAL; 39071f7990aSMaciej Strozek } 39171f7990aSMaciej Strozek } 39271f7990aSMaciej Strozek 39371f7990aSMaciej Strozek /** 39471f7990aSMaciej Strozek * sdca_fdl_process - Process the FDL state machine 39571f7990aSMaciej Strozek * @interrupt: SDCA interrupt structure 39671f7990aSMaciej Strozek * 39771f7990aSMaciej Strozek * Based on section 13.2.5 Flow Diagram for File Download, Host side. 39871f7990aSMaciej Strozek * 39971f7990aSMaciej Strozek * Return: Zero on success or a negative error code. 40071f7990aSMaciej Strozek */ 40171f7990aSMaciej Strozek int sdca_fdl_process(struct sdca_interrupt *interrupt) 40271f7990aSMaciej Strozek { 40371f7990aSMaciej Strozek struct device *dev = interrupt->dev; 40471f7990aSMaciej Strozek struct sdca_entity_xu *xu = &interrupt->entity->xu; 405e92e25f7SCharles Keepax struct fdl_state *fdl_state = interrupt->priv; 40671f7990aSMaciej Strozek unsigned int reg, status; 40771f7990aSMaciej Strozek int response, ret; 40871f7990aSMaciej Strozek 40971f7990aSMaciej Strozek ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap, 41071f7990aSMaciej Strozek interrupt->function, interrupt->entity, 41171f7990aSMaciej Strozek interrupt->control); 41271f7990aSMaciej Strozek if (ret) 41371f7990aSMaciej Strozek goto reset_function; 41471f7990aSMaciej Strozek 415e92e25f7SCharles Keepax sdca_ump_cancel_timeout(&fdl_state->timeout); 416e92e25f7SCharles Keepax 417cc58055bSCharles Keepax scoped_guard(mutex, &fdl_state->lock) { 418cc58055bSCharles Keepax reg = SDW_SDCA_CTL(interrupt->function->desc->adr, 419cc58055bSCharles Keepax interrupt->entity->id, SDCA_CTL_XU_FDL_STATUS, 0); 42071f7990aSMaciej Strozek ret = regmap_read(interrupt->function_regmap, reg, &status); 42171f7990aSMaciej Strozek if (ret < 0) { 42271f7990aSMaciej Strozek dev_err(dev, "failed to read FDL status: %d\n", ret); 42371f7990aSMaciej Strozek return ret; 42471f7990aSMaciej Strozek } 42571f7990aSMaciej Strozek 42671f7990aSMaciej Strozek dev_dbg(dev, "FDL status: %#x\n", status); 42771f7990aSMaciej Strozek 42871f7990aSMaciej Strozek ret = fdl_status_process(interrupt, status); 42971f7990aSMaciej Strozek if (ret < 0) 43071f7990aSMaciej Strozek goto reset_function; 43171f7990aSMaciej Strozek 43271f7990aSMaciej Strozek response = ret; 43371f7990aSMaciej Strozek 43471f7990aSMaciej Strozek dev_dbg(dev, "FDL response: %#x\n", response); 43571f7990aSMaciej Strozek 43671f7990aSMaciej Strozek ret = regmap_write(interrupt->function_regmap, reg, 43771f7990aSMaciej Strozek response | (status & ~SDCA_CTL_XU_FDLH_MASK)); 43871f7990aSMaciej Strozek if (ret < 0) { 43971f7990aSMaciej Strozek dev_err(dev, "failed to set FDL status signal: %d\n", ret); 44071f7990aSMaciej Strozek return ret; 44171f7990aSMaciej Strozek } 44271f7990aSMaciej Strozek 44371f7990aSMaciej Strozek ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap, 444cc58055bSCharles Keepax interrupt->function, 445cc58055bSCharles Keepax interrupt->entity, 44671f7990aSMaciej Strozek interrupt->control); 44771f7990aSMaciej Strozek if (ret) 44871f7990aSMaciej Strozek return ret; 44971f7990aSMaciej Strozek 45071f7990aSMaciej Strozek switch (response) { 45171f7990aSMaciej Strozek case SDCA_CTL_XU_FDLH_RESET_ACK: 45271f7990aSMaciej Strozek dev_dbg(dev, "FDL request reset\n"); 45371f7990aSMaciej Strozek 45471f7990aSMaciej Strozek switch (xu->reset_mechanism) { 45571f7990aSMaciej Strozek default: 45671f7990aSMaciej Strozek dev_warn(dev, "Requested reset mechanism not implemented\n"); 45771f7990aSMaciej Strozek fallthrough; 45871f7990aSMaciej Strozek case SDCA_XU_RESET_FUNCTION: 45971f7990aSMaciej Strozek goto reset_function; 46071f7990aSMaciej Strozek } 461e92e25f7SCharles Keepax case SDCA_CTL_XU_FDLH_COMPLETE: 462e92e25f7SCharles Keepax if (status & SDCA_CTL_XU_FDLD_REQ_ABORT || 463e92e25f7SCharles Keepax status == SDCA_CTL_XU_FDLD_COMPLETE) 464e92e25f7SCharles Keepax return 0; 465e92e25f7SCharles Keepax fallthrough; 46671f7990aSMaciej Strozek default: 467e92e25f7SCharles Keepax sdca_ump_schedule_timeout(&fdl_state->timeout, xu->max_delay); 46871f7990aSMaciej Strozek return 0; 46971f7990aSMaciej Strozek } 470cc58055bSCharles Keepax } 47171f7990aSMaciej Strozek 47271f7990aSMaciej Strozek reset_function: 47371f7990aSMaciej Strozek sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); 47471f7990aSMaciej Strozek 47571f7990aSMaciej Strozek return ret; 47671f7990aSMaciej Strozek } 47771f7990aSMaciej Strozek EXPORT_SYMBOL_NS_GPL(sdca_fdl_process, "SND_SOC_SDCA"); 47871f7990aSMaciej Strozek 47971f7990aSMaciej Strozek /** 48071f7990aSMaciej Strozek * sdca_fdl_alloc_state - allocate state for an FDL interrupt 48171f7990aSMaciej Strozek * @interrupt: SDCA interrupt structure. 48271f7990aSMaciej Strozek * 48371f7990aSMaciej Strozek * Return: Zero on success or a negative error code. 48471f7990aSMaciej Strozek */ 48571f7990aSMaciej Strozek int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) 48671f7990aSMaciej Strozek { 48771f7990aSMaciej Strozek struct device *dev = interrupt->dev; 48871f7990aSMaciej Strozek struct fdl_state *fdl_state; 48971f7990aSMaciej Strozek 49071f7990aSMaciej Strozek fdl_state = devm_kzalloc(dev, sizeof(struct fdl_state), GFP_KERNEL); 49171f7990aSMaciej Strozek if (!fdl_state) 49271f7990aSMaciej Strozek return -ENOMEM; 49371f7990aSMaciej Strozek 494e92e25f7SCharles Keepax INIT_DELAYED_WORK(&fdl_state->timeout, sdca_fdl_timeout_work); 4950723affaSCharles Keepax init_completion(&fdl_state->begin); 4960723affaSCharles Keepax init_completion(&fdl_state->done); 497e92e25f7SCharles Keepax mutex_init(&fdl_state->lock); 498e92e25f7SCharles Keepax fdl_state->interrupt = interrupt; 4990723affaSCharles Keepax 50071f7990aSMaciej Strozek interrupt->priv = fdl_state; 50171f7990aSMaciej Strozek 50271f7990aSMaciej Strozek return 0; 50371f7990aSMaciej Strozek } 50471f7990aSMaciej Strozek EXPORT_SYMBOL_NS_GPL(sdca_fdl_alloc_state, "SND_SOC_SDCA"); 505