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