162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2015, NVIDIA Corporation. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/platform_device.h> 762306a36Sopenharmony_ci#include <linux/dma-mapping.h> 862306a36Sopenharmony_ci#include <linux/firmware.h> 962306a36Sopenharmony_ci#include <linux/pci_ids.h> 1062306a36Sopenharmony_ci#include <linux/iopoll.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include "falcon.h" 1362306a36Sopenharmony_ci#include "drm.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cienum falcon_memory { 1662306a36Sopenharmony_ci FALCON_MEMORY_IMEM, 1762306a36Sopenharmony_ci FALCON_MEMORY_DATA, 1862306a36Sopenharmony_ci}; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic void falcon_writel(struct falcon *falcon, u32 value, u32 offset) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci writel(value, falcon->regs + offset); 2362306a36Sopenharmony_ci} 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ciint falcon_wait_idle(struct falcon *falcon) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci u32 value; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value, 3062306a36Sopenharmony_ci (value == 0), 10, 100000); 3162306a36Sopenharmony_ci} 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic int falcon_dma_wait_idle(struct falcon *falcon) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci u32 value; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci return readl_poll_timeout(falcon->regs + FALCON_DMATRFCMD, value, 3862306a36Sopenharmony_ci (value & FALCON_DMATRFCMD_IDLE), 10, 100000); 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic int falcon_copy_chunk(struct falcon *falcon, 4262306a36Sopenharmony_ci phys_addr_t base, 4362306a36Sopenharmony_ci unsigned long offset, 4462306a36Sopenharmony_ci enum falcon_memory target) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci u32 cmd = FALCON_DMATRFCMD_SIZE_256B; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci if (target == FALCON_MEMORY_IMEM) 4962306a36Sopenharmony_ci cmd |= FALCON_DMATRFCMD_IMEM; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci /* 5262306a36Sopenharmony_ci * Use second DMA context (i.e. the one for firmware). Strictly 5362306a36Sopenharmony_ci * speaking, at this point both DMA contexts point to the firmware 5462306a36Sopenharmony_ci * stream ID, but this register's value will be reused by the firmware 5562306a36Sopenharmony_ci * for later DMA transactions, so we need to use the correct value. 5662306a36Sopenharmony_ci */ 5762306a36Sopenharmony_ci cmd |= FALCON_DMATRFCMD_DMACTX(1); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci falcon_writel(falcon, offset, FALCON_DMATRFMOFFS); 6062306a36Sopenharmony_ci falcon_writel(falcon, base, FALCON_DMATRFFBOFFS); 6162306a36Sopenharmony_ci falcon_writel(falcon, cmd, FALCON_DMATRFCMD); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci return falcon_dma_wait_idle(falcon); 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic void falcon_copy_firmware_image(struct falcon *falcon, 6762306a36Sopenharmony_ci const struct firmware *firmware) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci u32 *virt = falcon->firmware.virt; 7062306a36Sopenharmony_ci size_t i; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci /* copy the whole thing taking into account endianness */ 7362306a36Sopenharmony_ci for (i = 0; i < firmware->size / sizeof(u32); i++) 7462306a36Sopenharmony_ci virt[i] = le32_to_cpu(((__le32 *)firmware->data)[i]); 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic int falcon_parse_firmware_image(struct falcon *falcon) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci struct falcon_fw_bin_header_v1 *bin = (void *)falcon->firmware.virt; 8062306a36Sopenharmony_ci struct falcon_fw_os_header_v1 *os; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci /* endian problems would show up right here */ 8362306a36Sopenharmony_ci if (bin->magic != PCI_VENDOR_ID_NVIDIA && bin->magic != 0x10fe) { 8462306a36Sopenharmony_ci dev_err(falcon->dev, "incorrect firmware magic\n"); 8562306a36Sopenharmony_ci return -EINVAL; 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci /* currently only version 1 is supported */ 8962306a36Sopenharmony_ci if (bin->version != 1) { 9062306a36Sopenharmony_ci dev_err(falcon->dev, "unsupported firmware version\n"); 9162306a36Sopenharmony_ci return -EINVAL; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* check that the firmware size is consistent */ 9562306a36Sopenharmony_ci if (bin->size > falcon->firmware.size) { 9662306a36Sopenharmony_ci dev_err(falcon->dev, "firmware image size inconsistency\n"); 9762306a36Sopenharmony_ci return -EINVAL; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci os = falcon->firmware.virt + bin->os_header_offset; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci falcon->firmware.bin_data.size = bin->os_size; 10362306a36Sopenharmony_ci falcon->firmware.bin_data.offset = bin->os_data_offset; 10462306a36Sopenharmony_ci falcon->firmware.code.offset = os->code_offset; 10562306a36Sopenharmony_ci falcon->firmware.code.size = os->code_size; 10662306a36Sopenharmony_ci falcon->firmware.data.offset = os->data_offset; 10762306a36Sopenharmony_ci falcon->firmware.data.size = os->data_size; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci return 0; 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ciint falcon_read_firmware(struct falcon *falcon, const char *name) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci int err; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* request_firmware prints error if it fails */ 11762306a36Sopenharmony_ci err = request_firmware(&falcon->firmware.firmware, name, falcon->dev); 11862306a36Sopenharmony_ci if (err < 0) 11962306a36Sopenharmony_ci return err; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci falcon->firmware.size = falcon->firmware.firmware->size; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci return 0; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ciint falcon_load_firmware(struct falcon *falcon) 12762306a36Sopenharmony_ci{ 12862306a36Sopenharmony_ci const struct firmware *firmware = falcon->firmware.firmware; 12962306a36Sopenharmony_ci int err; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* copy firmware image into local area. this also ensures endianness */ 13262306a36Sopenharmony_ci falcon_copy_firmware_image(falcon, firmware); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci /* parse the image data */ 13562306a36Sopenharmony_ci err = falcon_parse_firmware_image(falcon); 13662306a36Sopenharmony_ci if (err < 0) { 13762306a36Sopenharmony_ci dev_err(falcon->dev, "failed to parse firmware image\n"); 13862306a36Sopenharmony_ci return err; 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci release_firmware(firmware); 14262306a36Sopenharmony_ci falcon->firmware.firmware = NULL; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ciint falcon_init(struct falcon *falcon) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci falcon->firmware.virt = NULL; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return 0; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_civoid falcon_exit(struct falcon *falcon) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci if (falcon->firmware.firmware) 15762306a36Sopenharmony_ci release_firmware(falcon->firmware.firmware); 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ciint falcon_boot(struct falcon *falcon) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci unsigned long offset; 16362306a36Sopenharmony_ci u32 value; 16462306a36Sopenharmony_ci int err; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci if (!falcon->firmware.virt) 16762306a36Sopenharmony_ci return -EINVAL; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci err = readl_poll_timeout(falcon->regs + FALCON_DMACTL, value, 17062306a36Sopenharmony_ci (value & (FALCON_DMACTL_IMEM_SCRUBBING | 17162306a36Sopenharmony_ci FALCON_DMACTL_DMEM_SCRUBBING)) == 0, 17262306a36Sopenharmony_ci 10, 10000); 17362306a36Sopenharmony_ci if (err < 0) 17462306a36Sopenharmony_ci return err; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci falcon_writel(falcon, 0, FALCON_DMACTL); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci /* setup the address of the binary data so Falcon can access it later */ 17962306a36Sopenharmony_ci falcon_writel(falcon, (falcon->firmware.iova + 18062306a36Sopenharmony_ci falcon->firmware.bin_data.offset) >> 8, 18162306a36Sopenharmony_ci FALCON_DMATRFBASE); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci /* copy the data segment into Falcon internal memory */ 18462306a36Sopenharmony_ci for (offset = 0; offset < falcon->firmware.data.size; offset += 256) 18562306a36Sopenharmony_ci falcon_copy_chunk(falcon, 18662306a36Sopenharmony_ci falcon->firmware.data.offset + offset, 18762306a36Sopenharmony_ci offset, FALCON_MEMORY_DATA); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci /* copy the code segment into Falcon internal memory */ 19062306a36Sopenharmony_ci for (offset = 0; offset < falcon->firmware.code.size; offset += 256) 19162306a36Sopenharmony_ci falcon_copy_chunk(falcon, falcon->firmware.code.offset + offset, 19262306a36Sopenharmony_ci offset, FALCON_MEMORY_IMEM); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci /* setup falcon interrupts */ 19562306a36Sopenharmony_ci falcon_writel(falcon, FALCON_IRQMSET_EXT(0xff) | 19662306a36Sopenharmony_ci FALCON_IRQMSET_SWGEN1 | 19762306a36Sopenharmony_ci FALCON_IRQMSET_SWGEN0 | 19862306a36Sopenharmony_ci FALCON_IRQMSET_EXTERR | 19962306a36Sopenharmony_ci FALCON_IRQMSET_HALT | 20062306a36Sopenharmony_ci FALCON_IRQMSET_WDTMR, 20162306a36Sopenharmony_ci FALCON_IRQMSET); 20262306a36Sopenharmony_ci falcon_writel(falcon, FALCON_IRQDEST_EXT(0xff) | 20362306a36Sopenharmony_ci FALCON_IRQDEST_SWGEN1 | 20462306a36Sopenharmony_ci FALCON_IRQDEST_SWGEN0 | 20562306a36Sopenharmony_ci FALCON_IRQDEST_EXTERR | 20662306a36Sopenharmony_ci FALCON_IRQDEST_HALT, 20762306a36Sopenharmony_ci FALCON_IRQDEST); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci /* enable interface */ 21062306a36Sopenharmony_ci falcon_writel(falcon, FALCON_ITFEN_MTHDEN | 21162306a36Sopenharmony_ci FALCON_ITFEN_CTXEN, 21262306a36Sopenharmony_ci FALCON_ITFEN); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* boot falcon */ 21562306a36Sopenharmony_ci falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC); 21662306a36Sopenharmony_ci falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci err = falcon_wait_idle(falcon); 21962306a36Sopenharmony_ci if (err < 0) { 22062306a36Sopenharmony_ci dev_err(falcon->dev, "Falcon boot failed due to timeout\n"); 22162306a36Sopenharmony_ci return err; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci return 0; 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_civoid falcon_execute_method(struct falcon *falcon, u32 method, u32 data) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci falcon_writel(falcon, method >> 2, FALCON_UCLASS_METHOD_OFFSET); 23062306a36Sopenharmony_ci falcon_writel(falcon, data, FALCON_UCLASS_METHOD_DATA); 23162306a36Sopenharmony_ci} 232