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"); 119*e92e25f7SCharles 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"); 128*e92e25f7SCharles Keepax 129*e92e25f7SCharles Keepax error: 130*e92e25f7SCharles Keepax for (j = 0; j < SDCA_MAX_INTERRUPTS; j++) { 131*e92e25f7SCharles Keepax struct sdca_interrupt *interrupt = &info->irqs[j]; 132*e92e25f7SCharles Keepax struct fdl_state *fdl_state; 133*e92e25f7SCharles Keepax 134*e92e25f7SCharles Keepax if (interrupt->function != function || 135*e92e25f7SCharles Keepax !interrupt->entity || !interrupt->control || 136*e92e25f7SCharles Keepax interrupt->entity->type != SDCA_ENTITY_TYPE_XU || 137*e92e25f7SCharles Keepax interrupt->control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER) 138*e92e25f7SCharles Keepax continue; 139*e92e25f7SCharles Keepax 140*e92e25f7SCharles Keepax disable_irq(interrupt->irq); 141*e92e25f7SCharles Keepax 142*e92e25f7SCharles Keepax fdl_state = interrupt->priv; 143*e92e25f7SCharles Keepax 144*e92e25f7SCharles Keepax sdca_ump_cancel_timeout(&fdl_state->timeout); 145*e92e25f7SCharles Keepax } 146*e92e25f7SCharles 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 324*e92e25f7SCharles Keepax static void sdca_fdl_timeout_work(struct work_struct *work) 325*e92e25f7SCharles Keepax { 326*e92e25f7SCharles Keepax struct fdl_state *fdl_state = container_of(work, struct fdl_state, 327*e92e25f7SCharles Keepax timeout.work); 328*e92e25f7SCharles Keepax struct sdca_interrupt *interrupt = fdl_state->interrupt; 329*e92e25f7SCharles Keepax struct device *dev = interrupt->dev; 330*e92e25f7SCharles Keepax 331*e92e25f7SCharles Keepax dev_err(dev, "FDL transaction timed out\n"); 332*e92e25f7SCharles Keepax 333*e92e25f7SCharles Keepax guard(mutex)(&fdl_state->lock); 334*e92e25f7SCharles Keepax 335*e92e25f7SCharles Keepax fdl_end(interrupt); 336*e92e25f7SCharles Keepax sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); 337*e92e25f7SCharles Keepax } 338*e92e25f7SCharles 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; 401*e92e25f7SCharles Keepax struct fdl_state *fdl_state = interrupt->priv; 40271f7990aSMaciej Strozek unsigned int reg, status; 40371f7990aSMaciej Strozek int response, ret; 40471f7990aSMaciej Strozek 405*e92e25f7SCharles Keepax guard(mutex)(&fdl_state->lock); 406*e92e25f7SCharles Keepax 40771f7990aSMaciej Strozek ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap, 40871f7990aSMaciej Strozek interrupt->function, interrupt->entity, 40971f7990aSMaciej Strozek interrupt->control); 41071f7990aSMaciej Strozek if (ret) 41171f7990aSMaciej Strozek goto reset_function; 41271f7990aSMaciej Strozek 413*e92e25f7SCharles Keepax sdca_ump_cancel_timeout(&fdl_state->timeout); 414*e92e25f7SCharles Keepax 41571f7990aSMaciej Strozek reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, 41671f7990aSMaciej Strozek SDCA_CTL_XU_FDL_STATUS, 0); 41771f7990aSMaciej Strozek ret = regmap_read(interrupt->function_regmap, reg, &status); 41871f7990aSMaciej Strozek if (ret < 0) { 41971f7990aSMaciej Strozek dev_err(dev, "failed to read FDL status: %d\n", ret); 42071f7990aSMaciej Strozek return ret; 42171f7990aSMaciej Strozek } 42271f7990aSMaciej Strozek 42371f7990aSMaciej Strozek dev_dbg(dev, "FDL status: %#x\n", status); 42471f7990aSMaciej Strozek 42571f7990aSMaciej Strozek ret = fdl_status_process(interrupt, status); 42671f7990aSMaciej Strozek if (ret < 0) 42771f7990aSMaciej Strozek goto reset_function; 42871f7990aSMaciej Strozek 42971f7990aSMaciej Strozek response = ret; 43071f7990aSMaciej Strozek 43171f7990aSMaciej Strozek dev_dbg(dev, "FDL response: %#x\n", response); 43271f7990aSMaciej Strozek 43371f7990aSMaciej Strozek ret = regmap_write(interrupt->function_regmap, reg, 43471f7990aSMaciej Strozek response | (status & ~SDCA_CTL_XU_FDLH_MASK)); 43571f7990aSMaciej Strozek if (ret < 0) { 43671f7990aSMaciej Strozek dev_err(dev, "failed to set FDL status signal: %d\n", ret); 43771f7990aSMaciej Strozek return ret; 43871f7990aSMaciej Strozek } 43971f7990aSMaciej Strozek 44071f7990aSMaciej Strozek ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap, 44171f7990aSMaciej Strozek interrupt->function, 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 } 457*e92e25f7SCharles Keepax case SDCA_CTL_XU_FDLH_COMPLETE: 458*e92e25f7SCharles Keepax if (status & SDCA_CTL_XU_FDLD_REQ_ABORT || 459*e92e25f7SCharles Keepax status == SDCA_CTL_XU_FDLD_COMPLETE) 460*e92e25f7SCharles Keepax return 0; 461*e92e25f7SCharles Keepax fallthrough; 46271f7990aSMaciej Strozek default: 463*e92e25f7SCharles Keepax sdca_ump_schedule_timeout(&fdl_state->timeout, xu->max_delay); 46471f7990aSMaciej Strozek return 0; 46571f7990aSMaciej Strozek } 46671f7990aSMaciej Strozek 46771f7990aSMaciej Strozek reset_function: 46871f7990aSMaciej Strozek sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); 46971f7990aSMaciej Strozek 47071f7990aSMaciej Strozek return ret; 47171f7990aSMaciej Strozek } 47271f7990aSMaciej Strozek EXPORT_SYMBOL_NS_GPL(sdca_fdl_process, "SND_SOC_SDCA"); 47371f7990aSMaciej Strozek 47471f7990aSMaciej Strozek /** 47571f7990aSMaciej Strozek * sdca_fdl_alloc_state - allocate state for an FDL interrupt 47671f7990aSMaciej Strozek * @interrupt: SDCA interrupt structure. 47771f7990aSMaciej Strozek * 47871f7990aSMaciej Strozek * Return: Zero on success or a negative error code. 47971f7990aSMaciej Strozek */ 48071f7990aSMaciej Strozek int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) 48171f7990aSMaciej Strozek { 48271f7990aSMaciej Strozek struct device *dev = interrupt->dev; 48371f7990aSMaciej Strozek struct fdl_state *fdl_state; 48471f7990aSMaciej Strozek 48571f7990aSMaciej Strozek fdl_state = devm_kzalloc(dev, sizeof(struct fdl_state), GFP_KERNEL); 48671f7990aSMaciej Strozek if (!fdl_state) 48771f7990aSMaciej Strozek return -ENOMEM; 48871f7990aSMaciej Strozek 489*e92e25f7SCharles Keepax INIT_DELAYED_WORK(&fdl_state->timeout, sdca_fdl_timeout_work); 4900723affaSCharles Keepax init_completion(&fdl_state->begin); 4910723affaSCharles Keepax init_completion(&fdl_state->done); 492*e92e25f7SCharles Keepax mutex_init(&fdl_state->lock); 493*e92e25f7SCharles Keepax fdl_state->interrupt = interrupt; 4940723affaSCharles Keepax 49571f7990aSMaciej Strozek interrupt->priv = fdl_state; 49671f7990aSMaciej Strozek 49771f7990aSMaciej Strozek return 0; 49871f7990aSMaciej Strozek } 49971f7990aSMaciej Strozek EXPORT_SYMBOL_NS_GPL(sdca_fdl_alloc_state, "SND_SOC_SDCA"); 500