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> 17*0723affaSCharles 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 67*0723affaSCharles Keepax /** 68*0723affaSCharles Keepax * sdca_fdl_sync - wait for a function to finish FDL 69*0723affaSCharles Keepax * @dev: Device pointer for error messages. 70*0723affaSCharles Keepax * @function: Pointer to the SDCA Function. 71*0723affaSCharles Keepax * @info: Pointer to the SDCA interrupt info for this device. 72*0723affaSCharles Keepax * 73*0723affaSCharles Keepax * Return: Zero on success or a negative error code. 74*0723affaSCharles Keepax */ 75*0723affaSCharles Keepax int sdca_fdl_sync(struct device *dev, struct sdca_function_data *function, 76*0723affaSCharles Keepax struct sdca_interrupt_info *info) 77*0723affaSCharles Keepax { 78*0723affaSCharles Keepax static const int fdl_retries = 6; 79*0723affaSCharles Keepax unsigned long begin_timeout = msecs_to_jiffies(100); 80*0723affaSCharles Keepax unsigned long done_timeout = msecs_to_jiffies(4000); 81*0723affaSCharles Keepax int nfdl; 82*0723affaSCharles Keepax int i, j; 83*0723affaSCharles Keepax 84*0723affaSCharles Keepax for (i = 0; i < fdl_retries; i++) { 85*0723affaSCharles Keepax nfdl = 0; 86*0723affaSCharles Keepax 87*0723affaSCharles Keepax for (j = 0; j < SDCA_MAX_INTERRUPTS; j++) { 88*0723affaSCharles Keepax struct sdca_interrupt *interrupt = &info->irqs[j]; 89*0723affaSCharles Keepax struct fdl_state *fdl_state; 90*0723affaSCharles Keepax unsigned long time; 91*0723affaSCharles Keepax 92*0723affaSCharles Keepax if (interrupt->function != function || 93*0723affaSCharles Keepax !interrupt->entity || !interrupt->control || 94*0723affaSCharles Keepax interrupt->entity->type != SDCA_ENTITY_TYPE_XU || 95*0723affaSCharles Keepax interrupt->control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER) 96*0723affaSCharles Keepax continue; 97*0723affaSCharles Keepax 98*0723affaSCharles Keepax fdl_state = interrupt->priv; 99*0723affaSCharles Keepax nfdl++; 100*0723affaSCharles Keepax 101*0723affaSCharles Keepax /* 102*0723affaSCharles Keepax * Looking for timeout without any new FDL requests 103*0723affaSCharles Keepax * to imply the device has completed initial 104*0723affaSCharles Keepax * firmware setup. Alas the specification doesn't 105*0723affaSCharles Keepax * have any mechanism to detect this. 106*0723affaSCharles Keepax */ 107*0723affaSCharles Keepax time = wait_for_completion_timeout(&fdl_state->begin, 108*0723affaSCharles Keepax begin_timeout); 109*0723affaSCharles Keepax if (!time) { 110*0723affaSCharles Keepax dev_dbg(dev, "no new FDL starts\n"); 111*0723affaSCharles Keepax nfdl--; 112*0723affaSCharles Keepax continue; 113*0723affaSCharles Keepax } 114*0723affaSCharles Keepax 115*0723affaSCharles Keepax time = wait_for_completion_timeout(&fdl_state->done, 116*0723affaSCharles Keepax done_timeout); 117*0723affaSCharles Keepax if (!time) { 118*0723affaSCharles Keepax dev_err(dev, "timed out waiting for FDL to complete\n"); 119*0723affaSCharles Keepax return -ETIMEDOUT; 120*0723affaSCharles Keepax } 121*0723affaSCharles Keepax } 122*0723affaSCharles Keepax 123*0723affaSCharles Keepax if (!nfdl) 124*0723affaSCharles Keepax return 0; 125*0723affaSCharles Keepax } 126*0723affaSCharles Keepax 127*0723affaSCharles Keepax dev_err(dev, "too many FDL requests\n"); 128*0723affaSCharles Keepax return -ETIMEDOUT; 129*0723affaSCharles Keepax } 130*0723affaSCharles Keepax EXPORT_SYMBOL_NS_GPL(sdca_fdl_sync, "SND_SOC_SDCA"); 131*0723affaSCharles Keepax 13271f7990aSMaciej Strozek static char *fdl_get_sku_filename(struct device *dev, 13371f7990aSMaciej Strozek struct sdca_fdl_file *fdl_file) 13471f7990aSMaciej Strozek { 13571f7990aSMaciej Strozek struct device *parent = dev; 13671f7990aSMaciej Strozek const char *product_vendor; 13771f7990aSMaciej Strozek const char *product_sku; 13871f7990aSMaciej Strozek 13971f7990aSMaciej Strozek /* 14071f7990aSMaciej Strozek * Try to find pci_dev manually because the card may not be ready to be 14171f7990aSMaciej Strozek * used for snd_soc_card_get_pci_ssid yet 14271f7990aSMaciej Strozek */ 14371f7990aSMaciej Strozek while (parent) { 14471f7990aSMaciej Strozek if (dev_is_pci(parent)) { 14571f7990aSMaciej Strozek struct pci_dev *pci_dev = to_pci_dev(parent); 14671f7990aSMaciej Strozek 14771f7990aSMaciej Strozek return kasprintf(GFP_KERNEL, "sdca/%x/%x/%x/%x.bin", 14871f7990aSMaciej Strozek fdl_file->vendor_id, 14971f7990aSMaciej Strozek pci_dev->subsystem_vendor, 15071f7990aSMaciej Strozek pci_dev->subsystem_device, 15171f7990aSMaciej Strozek fdl_file->file_id); 15271f7990aSMaciej Strozek } else { 15371f7990aSMaciej Strozek parent = parent->parent; 15471f7990aSMaciej Strozek } 15571f7990aSMaciej Strozek } 15671f7990aSMaciej Strozek 15771f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_SYS_VENDOR); 15871f7990aSMaciej Strozek if (!product_vendor || !strcmp(product_vendor, "Default string")) 15971f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); 16071f7990aSMaciej Strozek if (!product_vendor || !strcmp(product_vendor, "Default string")) 16171f7990aSMaciej Strozek product_vendor = dmi_get_system_info(DMI_CHASSIS_VENDOR); 16271f7990aSMaciej Strozek if (!product_vendor) 16371f7990aSMaciej Strozek product_vendor = "unknown"; 16471f7990aSMaciej Strozek 16571f7990aSMaciej Strozek product_sku = dmi_get_system_info(DMI_PRODUCT_SKU); 16671f7990aSMaciej Strozek if (!product_sku || !strcmp(product_sku, "Default string")) 16771f7990aSMaciej Strozek product_sku = dmi_get_system_info(DMI_PRODUCT_NAME); 16871f7990aSMaciej Strozek if (!product_sku) 16971f7990aSMaciej Strozek product_sku = "unknown"; 17071f7990aSMaciej Strozek 17171f7990aSMaciej Strozek return kasprintf(GFP_KERNEL, "sdca/%x/%s/%s/%x.bin", fdl_file->vendor_id, 17271f7990aSMaciej Strozek product_vendor, product_sku, fdl_file->file_id); 17371f7990aSMaciej Strozek } 17471f7990aSMaciej Strozek 17571f7990aSMaciej Strozek static int fdl_load_file(struct sdca_interrupt *interrupt, 17671f7990aSMaciej Strozek struct sdca_fdl_set *set, int file_index) 17771f7990aSMaciej Strozek { 17871f7990aSMaciej Strozek struct device *dev = interrupt->dev; 17971f7990aSMaciej Strozek struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; 18071f7990aSMaciej Strozek const struct firmware *firmware = NULL; 18171f7990aSMaciej Strozek struct acpi_sw_file *swf = NULL, *tmp; 18271f7990aSMaciej Strozek struct sdca_fdl_file *fdl_file; 18371f7990aSMaciej Strozek char *disk_filename; 18471f7990aSMaciej Strozek int ret; 18571f7990aSMaciej Strozek int i; 18671f7990aSMaciej Strozek 18771f7990aSMaciej Strozek if (!set) { 18871f7990aSMaciej Strozek dev_err(dev, "request to load SWF with no set\n"); 18971f7990aSMaciej Strozek return -EINVAL; 19071f7990aSMaciej Strozek } 19171f7990aSMaciej Strozek 19271f7990aSMaciej Strozek fdl_file = &set->files[file_index]; 19371f7990aSMaciej Strozek 19471f7990aSMaciej Strozek if (fdl_data->swft) { 19571f7990aSMaciej Strozek tmp = fdl_data->swft->files; 19671f7990aSMaciej Strozek for (i = 0; i < fdl_data->swft->header.length; i += tmp->file_length, 19771f7990aSMaciej Strozek tmp = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length)) { 19871f7990aSMaciej Strozek if (tmp->vendor_id == fdl_file->vendor_id && 19971f7990aSMaciej Strozek tmp->file_id == fdl_file->file_id) { 20071f7990aSMaciej Strozek dev_dbg(dev, "located SWF in ACPI: %x-%x-%x\n", 20171f7990aSMaciej Strozek tmp->vendor_id, tmp->file_id, 20271f7990aSMaciej Strozek tmp->file_version); 20371f7990aSMaciej Strozek swf = tmp; 20471f7990aSMaciej Strozek break; 20571f7990aSMaciej Strozek } 20671f7990aSMaciej Strozek } 20771f7990aSMaciej Strozek } 20871f7990aSMaciej Strozek 20971f7990aSMaciej Strozek disk_filename = fdl_get_sku_filename(dev, fdl_file); 21071f7990aSMaciej Strozek if (!disk_filename) 21171f7990aSMaciej Strozek return -ENOMEM; 21271f7990aSMaciej Strozek 21371f7990aSMaciej Strozek dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); 21471f7990aSMaciej Strozek 21571f7990aSMaciej Strozek ret = firmware_request_nowarn(&firmware, disk_filename, dev); 21671f7990aSMaciej Strozek kfree(disk_filename); 21771f7990aSMaciej Strozek if (ret) { 21871f7990aSMaciej Strozek disk_filename = kasprintf(GFP_KERNEL, "sdca/%x/%x.bin", 21971f7990aSMaciej Strozek fdl_file->vendor_id, fdl_file->file_id); 22071f7990aSMaciej Strozek if (!disk_filename) 22171f7990aSMaciej Strozek return -ENOMEM; 22271f7990aSMaciej Strozek 22371f7990aSMaciej Strozek dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); 22471f7990aSMaciej Strozek 22571f7990aSMaciej Strozek ret = firmware_request_nowarn(&firmware, disk_filename, dev); 22671f7990aSMaciej Strozek kfree(disk_filename); 22771f7990aSMaciej Strozek } 22871f7990aSMaciej Strozek 22971f7990aSMaciej Strozek if (!ret) { 23071f7990aSMaciej Strozek tmp = (struct acpi_sw_file *)&firmware->data[0]; 23171f7990aSMaciej Strozek 23271f7990aSMaciej Strozek if (firmware->size < sizeof(*tmp) || 23371f7990aSMaciej Strozek tmp->file_length != firmware->size) { 23471f7990aSMaciej Strozek dev_err(dev, "bad disk SWF size\n"); 23571f7990aSMaciej Strozek } else if (!swf || swf->file_version <= tmp->file_version) { 23671f7990aSMaciej Strozek dev_dbg(dev, "using SWF from disk: %x-%x-%x\n", 23771f7990aSMaciej Strozek tmp->vendor_id, tmp->file_id, tmp->file_version); 23871f7990aSMaciej Strozek swf = tmp; 23971f7990aSMaciej Strozek } 24071f7990aSMaciej Strozek } 24171f7990aSMaciej Strozek 24271f7990aSMaciej Strozek if (!swf) { 24371f7990aSMaciej Strozek dev_err(dev, "failed to locate SWF\n"); 24471f7990aSMaciej Strozek return -ENOENT; 24571f7990aSMaciej Strozek } 24671f7990aSMaciej Strozek 24771f7990aSMaciej Strozek ret = sdca_ump_write_message(dev, interrupt->device_regmap, 24871f7990aSMaciej Strozek interrupt->function_regmap, 24971f7990aSMaciej Strozek interrupt->function, interrupt->entity, 25071f7990aSMaciej Strozek SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, 25171f7990aSMaciej Strozek SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, 25271f7990aSMaciej Strozek swf->file_length - offsetof(struct acpi_sw_file, data)); 25371f7990aSMaciej Strozek release_firmware(firmware); 25471f7990aSMaciej Strozek return ret; 25571f7990aSMaciej Strozek } 25671f7990aSMaciej Strozek 25771f7990aSMaciej Strozek static struct sdca_fdl_set *fdl_get_set(struct sdca_interrupt *interrupt) 25871f7990aSMaciej Strozek { 25971f7990aSMaciej Strozek struct device *dev = interrupt->dev; 26071f7990aSMaciej Strozek struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; 26171f7990aSMaciej Strozek struct sdca_entity *xu = interrupt->entity; 26271f7990aSMaciej Strozek struct sdca_control_range *range; 26371f7990aSMaciej Strozek unsigned int val; 26471f7990aSMaciej Strozek int i, ret; 26571f7990aSMaciej Strozek 26671f7990aSMaciej Strozek ret = regmap_read(interrupt->function_regmap, 26771f7990aSMaciej Strozek SDW_SDCA_CTL(interrupt->function->desc->adr, xu->id, 26871f7990aSMaciej Strozek SDCA_CTL_XU_FDL_SET_INDEX, 0), 26971f7990aSMaciej Strozek &val); 27071f7990aSMaciej Strozek if (ret < 0) { 27171f7990aSMaciej Strozek dev_err(dev, "failed to read FDL set index: %d\n", ret); 27271f7990aSMaciej Strozek return NULL; 27371f7990aSMaciej Strozek } 27471f7990aSMaciej Strozek 27571f7990aSMaciej Strozek range = sdca_selector_find_range(dev, xu, SDCA_CTL_XU_FDL_SET_INDEX, 27671f7990aSMaciej Strozek SDCA_FDL_SET_INDEX_NCOLS, 0); 27771f7990aSMaciej Strozek 27871f7990aSMaciej Strozek val = sdca_range_search(range, SDCA_FDL_SET_INDEX_SET_NUMBER, 27971f7990aSMaciej Strozek val, SDCA_FDL_SET_INDEX_FILE_SET_ID); 28071f7990aSMaciej Strozek 28171f7990aSMaciej Strozek for (i = 0; i < fdl_data->num_sets; i++) { 28271f7990aSMaciej Strozek if (fdl_data->sets[i].id == val) 28371f7990aSMaciej Strozek return &fdl_data->sets[i]; 28471f7990aSMaciej Strozek } 28571f7990aSMaciej Strozek 28671f7990aSMaciej Strozek dev_err(dev, "invalid fileset id: %d\n", val); 28771f7990aSMaciej Strozek return NULL; 28871f7990aSMaciej Strozek } 28971f7990aSMaciej Strozek 29071f7990aSMaciej Strozek static void fdl_end(struct sdca_interrupt *interrupt) 29171f7990aSMaciej Strozek { 29271f7990aSMaciej Strozek struct fdl_state *fdl_state = interrupt->priv; 29371f7990aSMaciej Strozek 29471f7990aSMaciej Strozek if (!fdl_state->set) 29571f7990aSMaciej Strozek return; 29671f7990aSMaciej Strozek 29771f7990aSMaciej Strozek fdl_state->set = NULL; 29871f7990aSMaciej Strozek 299*0723affaSCharles Keepax pm_runtime_put(interrupt->dev); 300*0723affaSCharles Keepax complete(&fdl_state->done); 301*0723affaSCharles Keepax 30271f7990aSMaciej Strozek dev_dbg(interrupt->dev, "completed FDL process\n"); 30371f7990aSMaciej Strozek } 30471f7990aSMaciej Strozek 30571f7990aSMaciej Strozek static int fdl_status_process(struct sdca_interrupt *interrupt, unsigned int status) 30671f7990aSMaciej Strozek { 30771f7990aSMaciej Strozek struct fdl_state *fdl_state = interrupt->priv; 30871f7990aSMaciej Strozek int ret; 30971f7990aSMaciej Strozek 31071f7990aSMaciej Strozek switch (status) { 31171f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_NEEDS_SET: 31271f7990aSMaciej Strozek dev_dbg(interrupt->dev, "starting FDL process...\n"); 31371f7990aSMaciej Strozek 314*0723affaSCharles Keepax pm_runtime_get(interrupt->dev); 315*0723affaSCharles Keepax complete(&fdl_state->begin); 316*0723affaSCharles Keepax 31771f7990aSMaciej Strozek fdl_state->file_index = 0; 31871f7990aSMaciej Strozek fdl_state->set = fdl_get_set(interrupt); 31971f7990aSMaciej Strozek fallthrough; 32071f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_MORE_FILES_OK: 32171f7990aSMaciej Strozek ret = fdl_load_file(interrupt, fdl_state->set, fdl_state->file_index); 32271f7990aSMaciej Strozek if (ret) { 32371f7990aSMaciej Strozek fdl_end(interrupt); 32471f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_REQ_ABORT; 32571f7990aSMaciej Strozek } 32671f7990aSMaciej Strozek 32771f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_FILE_AVAILABLE; 32871f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_FILE_OK: 32971f7990aSMaciej Strozek if (!fdl_state->set) { 33071f7990aSMaciej Strozek fdl_end(interrupt); 33171f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_REQ_ABORT; 33271f7990aSMaciej Strozek } 33371f7990aSMaciej Strozek 33471f7990aSMaciej Strozek fdl_state->file_index++; 33571f7990aSMaciej Strozek 33671f7990aSMaciej Strozek if (fdl_state->file_index < fdl_state->set->num_files) 33771f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_MORE_FILES; 33871f7990aSMaciej Strozek fallthrough; 33971f7990aSMaciej Strozek case SDCA_CTL_XU_FDLD_COMPLETE: 34071f7990aSMaciej Strozek fdl_end(interrupt); 34171f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_COMPLETE; 34271f7990aSMaciej Strozek default: 34371f7990aSMaciej Strozek fdl_end(interrupt); 34471f7990aSMaciej Strozek 34571f7990aSMaciej Strozek if (status & SDCA_CTL_XU_FDLD_REQ_RESET) 34671f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_RESET_ACK; 34771f7990aSMaciej Strozek else if (status & SDCA_CTL_XU_FDLD_REQ_ABORT) 34871f7990aSMaciej Strozek return SDCA_CTL_XU_FDLH_COMPLETE; 34971f7990aSMaciej Strozek 35071f7990aSMaciej Strozek dev_err(interrupt->dev, "invalid FDL status: %x\n", status); 35171f7990aSMaciej Strozek return -EINVAL; 35271f7990aSMaciej Strozek } 35371f7990aSMaciej Strozek } 35471f7990aSMaciej Strozek 35571f7990aSMaciej Strozek /** 35671f7990aSMaciej Strozek * sdca_fdl_process - Process the FDL state machine 35771f7990aSMaciej Strozek * @interrupt: SDCA interrupt structure 35871f7990aSMaciej Strozek * 35971f7990aSMaciej Strozek * Based on section 13.2.5 Flow Diagram for File Download, Host side. 36071f7990aSMaciej Strozek * 36171f7990aSMaciej Strozek * Return: Zero on success or a negative error code. 36271f7990aSMaciej Strozek */ 36371f7990aSMaciej Strozek int sdca_fdl_process(struct sdca_interrupt *interrupt) 36471f7990aSMaciej Strozek { 36571f7990aSMaciej Strozek struct device *dev = interrupt->dev; 36671f7990aSMaciej Strozek struct sdca_entity_xu *xu = &interrupt->entity->xu; 36771f7990aSMaciej Strozek unsigned int reg, status; 36871f7990aSMaciej Strozek int response, ret; 36971f7990aSMaciej Strozek 37071f7990aSMaciej Strozek ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap, 37171f7990aSMaciej Strozek interrupt->function, interrupt->entity, 37271f7990aSMaciej Strozek interrupt->control); 37371f7990aSMaciej Strozek if (ret) 37471f7990aSMaciej Strozek goto reset_function; 37571f7990aSMaciej Strozek 37671f7990aSMaciej Strozek reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, 37771f7990aSMaciej Strozek SDCA_CTL_XU_FDL_STATUS, 0); 37871f7990aSMaciej Strozek ret = regmap_read(interrupt->function_regmap, reg, &status); 37971f7990aSMaciej Strozek if (ret < 0) { 38071f7990aSMaciej Strozek dev_err(dev, "failed to read FDL status: %d\n", ret); 38171f7990aSMaciej Strozek return ret; 38271f7990aSMaciej Strozek } 38371f7990aSMaciej Strozek 38471f7990aSMaciej Strozek dev_dbg(dev, "FDL status: %#x\n", status); 38571f7990aSMaciej Strozek 38671f7990aSMaciej Strozek ret = fdl_status_process(interrupt, status); 38771f7990aSMaciej Strozek if (ret < 0) 38871f7990aSMaciej Strozek goto reset_function; 38971f7990aSMaciej Strozek 39071f7990aSMaciej Strozek response = ret; 39171f7990aSMaciej Strozek 39271f7990aSMaciej Strozek dev_dbg(dev, "FDL response: %#x\n", response); 39371f7990aSMaciej Strozek 39471f7990aSMaciej Strozek ret = regmap_write(interrupt->function_regmap, reg, 39571f7990aSMaciej Strozek response | (status & ~SDCA_CTL_XU_FDLH_MASK)); 39671f7990aSMaciej Strozek if (ret < 0) { 39771f7990aSMaciej Strozek dev_err(dev, "failed to set FDL status signal: %d\n", ret); 39871f7990aSMaciej Strozek return ret; 39971f7990aSMaciej Strozek } 40071f7990aSMaciej Strozek 40171f7990aSMaciej Strozek ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap, 40271f7990aSMaciej Strozek interrupt->function, interrupt->entity, 40371f7990aSMaciej Strozek interrupt->control); 40471f7990aSMaciej Strozek if (ret) 40571f7990aSMaciej Strozek return ret; 40671f7990aSMaciej Strozek 40771f7990aSMaciej Strozek switch (response) { 40871f7990aSMaciej Strozek case SDCA_CTL_XU_FDLH_RESET_ACK: 40971f7990aSMaciej Strozek dev_dbg(dev, "FDL request reset\n"); 41071f7990aSMaciej Strozek 41171f7990aSMaciej Strozek switch (xu->reset_mechanism) { 41271f7990aSMaciej Strozek default: 41371f7990aSMaciej Strozek dev_warn(dev, "Requested reset mechanism not implemented\n"); 41471f7990aSMaciej Strozek fallthrough; 41571f7990aSMaciej Strozek case SDCA_XU_RESET_FUNCTION: 41671f7990aSMaciej Strozek goto reset_function; 41771f7990aSMaciej Strozek } 41871f7990aSMaciej Strozek default: 41971f7990aSMaciej Strozek return 0; 42071f7990aSMaciej Strozek } 42171f7990aSMaciej Strozek 42271f7990aSMaciej Strozek reset_function: 42371f7990aSMaciej Strozek sdca_reset_function(dev, interrupt->function, interrupt->function_regmap); 42471f7990aSMaciej Strozek 42571f7990aSMaciej Strozek return ret; 42671f7990aSMaciej Strozek } 42771f7990aSMaciej Strozek EXPORT_SYMBOL_NS_GPL(sdca_fdl_process, "SND_SOC_SDCA"); 42871f7990aSMaciej Strozek 42971f7990aSMaciej Strozek /** 43071f7990aSMaciej Strozek * sdca_fdl_alloc_state - allocate state for an FDL interrupt 43171f7990aSMaciej Strozek * @interrupt: SDCA interrupt structure. 43271f7990aSMaciej Strozek * 43371f7990aSMaciej Strozek * Return: Zero on success or a negative error code. 43471f7990aSMaciej Strozek */ 43571f7990aSMaciej Strozek int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) 43671f7990aSMaciej Strozek { 43771f7990aSMaciej Strozek struct device *dev = interrupt->dev; 43871f7990aSMaciej Strozek struct fdl_state *fdl_state; 43971f7990aSMaciej Strozek 44071f7990aSMaciej Strozek fdl_state = devm_kzalloc(dev, sizeof(struct fdl_state), GFP_KERNEL); 44171f7990aSMaciej Strozek if (!fdl_state) 44271f7990aSMaciej Strozek return -ENOMEM; 44371f7990aSMaciej Strozek 444*0723affaSCharles Keepax init_completion(&fdl_state->begin); 445*0723affaSCharles Keepax init_completion(&fdl_state->done); 446*0723affaSCharles Keepax 44771f7990aSMaciej Strozek interrupt->priv = fdl_state; 44871f7990aSMaciej Strozek 44971f7990aSMaciej Strozek return 0; 45071f7990aSMaciej Strozek } 45171f7990aSMaciej Strozek EXPORT_SYMBOL_NS_GPL(sdca_fdl_alloc_state, "SND_SOC_SDCA"); 452