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