18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2015, NVIDIA Corporation. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 78c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 88c2ecf20Sopenharmony_ci#include <linux/firmware.h> 98c2ecf20Sopenharmony_ci#include <linux/pci_ids.h> 108c2ecf20Sopenharmony_ci#include <linux/iopoll.h> 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include "falcon.h" 138c2ecf20Sopenharmony_ci#include "drm.h" 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cienum falcon_memory { 168c2ecf20Sopenharmony_ci FALCON_MEMORY_IMEM, 178c2ecf20Sopenharmony_ci FALCON_MEMORY_DATA, 188c2ecf20Sopenharmony_ci}; 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistatic void falcon_writel(struct falcon *falcon, u32 value, u32 offset) 218c2ecf20Sopenharmony_ci{ 228c2ecf20Sopenharmony_ci writel(value, falcon->regs + offset); 238c2ecf20Sopenharmony_ci} 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ciint falcon_wait_idle(struct falcon *falcon) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci u32 value; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value, 308c2ecf20Sopenharmony_ci (value == 0), 10, 100000); 318c2ecf20Sopenharmony_ci} 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic int falcon_dma_wait_idle(struct falcon *falcon) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci u32 value; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci return readl_poll_timeout(falcon->regs + FALCON_DMATRFCMD, value, 388c2ecf20Sopenharmony_ci (value & FALCON_DMATRFCMD_IDLE), 10, 100000); 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic int falcon_copy_chunk(struct falcon *falcon, 428c2ecf20Sopenharmony_ci phys_addr_t base, 438c2ecf20Sopenharmony_ci unsigned long offset, 448c2ecf20Sopenharmony_ci enum falcon_memory target) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci u32 cmd = FALCON_DMATRFCMD_SIZE_256B; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci if (target == FALCON_MEMORY_IMEM) 498c2ecf20Sopenharmony_ci cmd |= FALCON_DMATRFCMD_IMEM; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci falcon_writel(falcon, offset, FALCON_DMATRFMOFFS); 528c2ecf20Sopenharmony_ci falcon_writel(falcon, base, FALCON_DMATRFFBOFFS); 538c2ecf20Sopenharmony_ci falcon_writel(falcon, cmd, FALCON_DMATRFCMD); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci return falcon_dma_wait_idle(falcon); 568c2ecf20Sopenharmony_ci} 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic void falcon_copy_firmware_image(struct falcon *falcon, 598c2ecf20Sopenharmony_ci const struct firmware *firmware) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci u32 *virt = falcon->firmware.virt; 628c2ecf20Sopenharmony_ci size_t i; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* copy the whole thing taking into account endianness */ 658c2ecf20Sopenharmony_ci for (i = 0; i < firmware->size / sizeof(u32); i++) 668c2ecf20Sopenharmony_ci virt[i] = le32_to_cpu(((u32 *)firmware->data)[i]); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int falcon_parse_firmware_image(struct falcon *falcon) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci struct falcon_fw_bin_header_v1 *bin = (void *)falcon->firmware.virt; 728c2ecf20Sopenharmony_ci struct falcon_fw_os_header_v1 *os; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* endian problems would show up right here */ 758c2ecf20Sopenharmony_ci if (bin->magic != PCI_VENDOR_ID_NVIDIA) { 768c2ecf20Sopenharmony_ci dev_err(falcon->dev, "incorrect firmware magic\n"); 778c2ecf20Sopenharmony_ci return -EINVAL; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* currently only version 1 is supported */ 818c2ecf20Sopenharmony_ci if (bin->version != 1) { 828c2ecf20Sopenharmony_ci dev_err(falcon->dev, "unsupported firmware version\n"); 838c2ecf20Sopenharmony_ci return -EINVAL; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci /* check that the firmware size is consistent */ 878c2ecf20Sopenharmony_ci if (bin->size > falcon->firmware.size) { 888c2ecf20Sopenharmony_ci dev_err(falcon->dev, "firmware image size inconsistency\n"); 898c2ecf20Sopenharmony_ci return -EINVAL; 908c2ecf20Sopenharmony_ci } 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci os = falcon->firmware.virt + bin->os_header_offset; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci falcon->firmware.bin_data.size = bin->os_size; 958c2ecf20Sopenharmony_ci falcon->firmware.bin_data.offset = bin->os_data_offset; 968c2ecf20Sopenharmony_ci falcon->firmware.code.offset = os->code_offset; 978c2ecf20Sopenharmony_ci falcon->firmware.code.size = os->code_size; 988c2ecf20Sopenharmony_ci falcon->firmware.data.offset = os->data_offset; 998c2ecf20Sopenharmony_ci falcon->firmware.data.size = os->data_size; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci return 0; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ciint falcon_read_firmware(struct falcon *falcon, const char *name) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci int err; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci /* request_firmware prints error if it fails */ 1098c2ecf20Sopenharmony_ci err = request_firmware(&falcon->firmware.firmware, name, falcon->dev); 1108c2ecf20Sopenharmony_ci if (err < 0) 1118c2ecf20Sopenharmony_ci return err; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci falcon->firmware.size = falcon->firmware.firmware->size; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci return 0; 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ciint falcon_load_firmware(struct falcon *falcon) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci const struct firmware *firmware = falcon->firmware.firmware; 1218c2ecf20Sopenharmony_ci int err; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* copy firmware image into local area. this also ensures endianness */ 1248c2ecf20Sopenharmony_ci falcon_copy_firmware_image(falcon, firmware); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci /* parse the image data */ 1278c2ecf20Sopenharmony_ci err = falcon_parse_firmware_image(falcon); 1288c2ecf20Sopenharmony_ci if (err < 0) { 1298c2ecf20Sopenharmony_ci dev_err(falcon->dev, "failed to parse firmware image\n"); 1308c2ecf20Sopenharmony_ci return err; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci release_firmware(firmware); 1348c2ecf20Sopenharmony_ci falcon->firmware.firmware = NULL; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci return 0; 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ciint falcon_init(struct falcon *falcon) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci falcon->firmware.virt = NULL; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci return 0; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_civoid falcon_exit(struct falcon *falcon) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci if (falcon->firmware.firmware) 1498c2ecf20Sopenharmony_ci release_firmware(falcon->firmware.firmware); 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ciint falcon_boot(struct falcon *falcon) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci unsigned long offset; 1558c2ecf20Sopenharmony_ci u32 value; 1568c2ecf20Sopenharmony_ci int err; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci if (!falcon->firmware.virt) 1598c2ecf20Sopenharmony_ci return -EINVAL; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci err = readl_poll_timeout(falcon->regs + FALCON_DMACTL, value, 1628c2ecf20Sopenharmony_ci (value & (FALCON_DMACTL_IMEM_SCRUBBING | 1638c2ecf20Sopenharmony_ci FALCON_DMACTL_DMEM_SCRUBBING)) == 0, 1648c2ecf20Sopenharmony_ci 10, 10000); 1658c2ecf20Sopenharmony_ci if (err < 0) 1668c2ecf20Sopenharmony_ci return err; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci falcon_writel(falcon, 0, FALCON_DMACTL); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* setup the address of the binary data so Falcon can access it later */ 1718c2ecf20Sopenharmony_ci falcon_writel(falcon, (falcon->firmware.iova + 1728c2ecf20Sopenharmony_ci falcon->firmware.bin_data.offset) >> 8, 1738c2ecf20Sopenharmony_ci FALCON_DMATRFBASE); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci /* copy the data segment into Falcon internal memory */ 1768c2ecf20Sopenharmony_ci for (offset = 0; offset < falcon->firmware.data.size; offset += 256) 1778c2ecf20Sopenharmony_ci falcon_copy_chunk(falcon, 1788c2ecf20Sopenharmony_ci falcon->firmware.data.offset + offset, 1798c2ecf20Sopenharmony_ci offset, FALCON_MEMORY_DATA); 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci /* copy the first code segment into Falcon internal memory */ 1828c2ecf20Sopenharmony_ci falcon_copy_chunk(falcon, falcon->firmware.code.offset, 1838c2ecf20Sopenharmony_ci 0, FALCON_MEMORY_IMEM); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci /* setup falcon interrupts */ 1868c2ecf20Sopenharmony_ci falcon_writel(falcon, FALCON_IRQMSET_EXT(0xff) | 1878c2ecf20Sopenharmony_ci FALCON_IRQMSET_SWGEN1 | 1888c2ecf20Sopenharmony_ci FALCON_IRQMSET_SWGEN0 | 1898c2ecf20Sopenharmony_ci FALCON_IRQMSET_EXTERR | 1908c2ecf20Sopenharmony_ci FALCON_IRQMSET_HALT | 1918c2ecf20Sopenharmony_ci FALCON_IRQMSET_WDTMR, 1928c2ecf20Sopenharmony_ci FALCON_IRQMSET); 1938c2ecf20Sopenharmony_ci falcon_writel(falcon, FALCON_IRQDEST_EXT(0xff) | 1948c2ecf20Sopenharmony_ci FALCON_IRQDEST_SWGEN1 | 1958c2ecf20Sopenharmony_ci FALCON_IRQDEST_SWGEN0 | 1968c2ecf20Sopenharmony_ci FALCON_IRQDEST_EXTERR | 1978c2ecf20Sopenharmony_ci FALCON_IRQDEST_HALT, 1988c2ecf20Sopenharmony_ci FALCON_IRQDEST); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci /* enable interface */ 2018c2ecf20Sopenharmony_ci falcon_writel(falcon, FALCON_ITFEN_MTHDEN | 2028c2ecf20Sopenharmony_ci FALCON_ITFEN_CTXEN, 2038c2ecf20Sopenharmony_ci FALCON_ITFEN); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci /* boot falcon */ 2068c2ecf20Sopenharmony_ci falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC); 2078c2ecf20Sopenharmony_ci falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci err = falcon_wait_idle(falcon); 2108c2ecf20Sopenharmony_ci if (err < 0) { 2118c2ecf20Sopenharmony_ci dev_err(falcon->dev, "Falcon boot failed due to timeout\n"); 2128c2ecf20Sopenharmony_ci return err; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci return 0; 2168c2ecf20Sopenharmony_ci} 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_civoid falcon_execute_method(struct falcon *falcon, u32 method, u32 data) 2198c2ecf20Sopenharmony_ci{ 2208c2ecf20Sopenharmony_ci falcon_writel(falcon, method >> 2, FALCON_UCLASS_METHOD_OFFSET); 2218c2ecf20Sopenharmony_ci falcon_writel(falcon, data, FALCON_UCLASS_METHOD_DATA); 2228c2ecf20Sopenharmony_ci} 223