diff --git a/drivers/video/tegra/host/nvdla/Makefile b/drivers/video/tegra/host/nvdla/Makefile index 29acab3c..46b3245c 100644 --- a/drivers/video/tegra/host/nvdla/Makefile +++ b/drivers/video/tegra/host/nvdla/Makefile @@ -23,7 +23,8 @@ nvhost-nvdla-objs += \ $(NVDLA_COMMON_OBJS) \ port/device/nvdla_device_host1x.o \ port/fw/nvdla_fw_flcn.o \ - port/sync/nvdla_sync_syncpt.o + port/sync/nvdla_sync_syncpt.o \ + port/fw/nvdla_falcon.o ifdef CONFIG_TEGRA_GRHOST nvhost-nvdla-objs += dla_channel.o diff --git a/drivers/video/tegra/host/nvdla/port/fw/nvdla_falcon.c b/drivers/video/tegra/host/nvdla/port/fw/nvdla_falcon.c new file mode 100644 index 00000000..86b5d2dc --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/fw/nvdla_falcon.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2023, NVIDIA Corporation. All rights reserved. + */ + +#include "nvdla_falcon.h" +#include "../../port/nvdla_host_wrapper.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +enum falcon_memory { + FALCON_MEMORY_IMEM, + FALCON_MEMORY_DATA, +}; + +static void falcon_writel(struct falcon *falcon, u32 value, u32 offset) +{ + writel(value, falcon->regs + offset); +} + +static int falcon_wait_idle(struct falcon *falcon) +{ + u32 value; + + return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value, + (value == 0), 10, 100000); +} + +static int falcon_dma_wait_idle(struct falcon *falcon) +{ + u32 value; + + return readl_poll_timeout(falcon->regs + FALCON_DMATRFCMD, value, + (value & FALCON_DMATRFCMD_IDLE), 10, 100000); +} + +static int falcon_copy_chunk(struct falcon *falcon, + phys_addr_t base, + unsigned long offset, + enum falcon_memory target) +{ + u32 cmd = FALCON_DMATRFCMD_SIZE_256B; + + if (target == FALCON_MEMORY_IMEM) + cmd |= FALCON_DMATRFCMD_IMEM; + + falcon_writel(falcon, offset, FALCON_DMATRFMOFFS); + falcon_writel(falcon, base, FALCON_DMATRFFBOFFS); + falcon_writel(falcon, cmd, FALCON_DMATRFCMD); + + return falcon_dma_wait_idle(falcon); +} + +static void falcon_copy_firmware_image(struct falcon *falcon, + const struct firmware *firmware) +{ + u32 *virt = falcon->firmware.virt; + size_t i; + + /* copy the whole thing taking into account endianness */ + for (i = 0; i < firmware->size / sizeof(u32); i++) + virt[i] = le32_to_cpu(((__le32 *)firmware->data)[i]); +} + +static int falcon_parse_firmware_image(struct falcon *falcon) +{ + struct falcon_fw_bin_header_v1 *bin = (void *)falcon->firmware.virt; + struct falcon_fw_os_header_v1 *os; + + /* endian problems would show up right here */ + if (bin->magic != PCI_VENDOR_ID_NVIDIA && bin->magic != 0x10fe) { + dev_err(falcon->dev, "incorrect firmware magic\n"); + return -EINVAL; + } + + /* currently only version 1 is supported */ + if (bin->version != 1) { + dev_err(falcon->dev, "unsupported firmware version\n"); + return -EINVAL; + } + + /* check that the firmware size is consistent */ + if (bin->size > falcon->firmware.size) { + dev_err(falcon->dev, "firmware image size inconsistency\n"); + return -EINVAL; + } + + os = falcon->firmware.virt + bin->os_header_offset; + + falcon->firmware.bin_data.size = bin->os_size; + falcon->firmware.bin_data.offset = bin->os_data_offset; + falcon->firmware.code.offset = os->code_offset; + falcon->firmware.code.size = os->code_size; + falcon->firmware.data.offset = os->data_offset; + falcon->firmware.data.size = os->data_size; + + return 0; +} + +static int falcon_read_firmware(struct falcon *falcon, const char *name) +{ + int err, retry_count=10; + +retry_request: + /* request_firmware prints error if it fails */ + err = request_firmware_direct(&falcon->firmware.firmware, name, falcon->dev); + if (err == -EINTR && retry_count) { + retry_count--; + goto retry_request; + } + + if (err < 0) + return err; + + falcon->firmware.size = falcon->firmware.firmware->size; + + return 0; +} + +static int falcon_load_firmware(struct falcon *falcon) +{ + const struct firmware *firmware = falcon->firmware.firmware; + int err; + + /* copy firmware image into local area. this also ensures endianness */ + falcon_copy_firmware_image(falcon, firmware); + + /* parse the image data */ + err = falcon_parse_firmware_image(falcon); + if (err < 0) { + dev_err(falcon->dev, "failed to parse firmware image\n"); + return err; + } + + release_firmware(firmware); + falcon->firmware.firmware = NULL; + + return 0; +} + +static int falcon_init(struct falcon *falcon) +{ + falcon->firmware.virt = NULL; + + return 0; +} + +static int falcon_boot(struct falcon *falcon) +{ + unsigned long offset; + u32 value; + int err; + + if (!falcon->firmware.virt) + return -EINVAL; + + err = readl_poll_timeout(falcon->regs + FALCON_DMACTL, value, + (value & (FALCON_DMACTL_IMEM_SCRUBBING | + FALCON_DMACTL_DMEM_SCRUBBING)) == 0, + 10, 10000); + if (err < 0) + return err; + + falcon_writel(falcon, 0, FALCON_DMACTL); + + /* setup the address of the binary data so Falcon can access it later */ + falcon_writel(falcon, (falcon->firmware.iova + + falcon->firmware.bin_data.offset) >> 8, + FALCON_DMATRFBASE); + + /* copy the data segment into Falcon internal memory */ + for (offset = 0; offset < falcon->firmware.data.size; offset += 256) + falcon_copy_chunk(falcon, + falcon->firmware.data.offset + offset, + offset, FALCON_MEMORY_DATA); + + /* copy the code segment into Falcon internal memory */ + for (offset = 0; offset < falcon->firmware.code.size; offset += 256) + falcon_copy_chunk(falcon, falcon->firmware.code.offset + offset, + offset, FALCON_MEMORY_IMEM); + + /* enable interface */ + falcon_writel(falcon, FALCON_ITFEN_MTHDEN | + FALCON_ITFEN_CTXEN, + FALCON_ITFEN); + + /* boot falcon */ + falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC); + falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL); + + err = falcon_wait_idle(falcon); + if (err < 0) { + dev_err(falcon->dev, "Falcon boot failed due to timeout\n"); + return err; + } + + return 0; +} + +/* NVDLA Falcon API implementations */ + +/** + * ISR for falcon + */ +static irqreturn_t nvdla_flcn_isr(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct nvhost_device_data *pdata = platform_get_drvdata(pdev); + + if (pdata->flcn_isr) + pdata->flcn_isr(pdev); + + return IRQ_HANDLED; +} + +/** + * Initialize the interrupt for DLA falcon + */ +int nvdla_flcn_intr_init(struct platform_device *pdev) +{ + struct nvhost_device_data *pdata = platform_get_drvdata(pdev); + int ret = 0; + + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq < 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + return -ENXIO; + } + + ret = devm_request_irq(&pdev->dev, pdata->irq, nvdla_flcn_isr, 0, + dev_name(&pdev->dev), pdev); + if (ret) { + dev_err(&pdev->dev, "failed to request irq. err %d\n", ret); + return ret; + } + + /* keep irq disabled */ + disable_irq(pdata->irq); + + return 0; +} + +/** + * Reload firmware for DLA falcon + */ +int nvdla_flcn_reload_fw(struct platform_device *pdev) +{ + /* TODO: Used by debugfs */ + return -EOPNOTSUPP; +} + +/** + * Initialize DLA falcon + */ +static int nvdla_flcn_init(struct platform_device *pdev, + struct nvhost_device_data *pdata) +{ + struct falcon *falcon; + + falcon = devm_kzalloc(&pdev->dev, sizeof(*falcon), GFP_KERNEL); + if (!falcon) + return -ENOMEM; + + falcon->dev = &pdev->dev; + falcon->regs = pdata->aperture[0]; + + falcon_init(falcon); + + pdata->falcon_data = falcon; + + return 0; +} + +/** + * Prepare DLA falcon for power off + */ +int nvdla_flcn_prepare_poweroff(struct platform_device *pdev) +{ + struct nvhost_device_data *pdata = platform_get_drvdata(pdev); + + if (pdata->flcn_isr) + disable_irq(pdata->irq); + + return 0; +} + +/** + * Load firmware for DLA falcon + */ +static int nvdla_flcn_load_firmware(struct platform_device *pdev, + struct falcon *falcon, + char *firmware_name) +{ + dma_addr_t iova; + size_t size; + void *virt; + int err; + + if (falcon->firmware.virt) + return 0; + + err = falcon_read_firmware(falcon, firmware_name); + if (err < 0) + return err; + + size = falcon->firmware.size; + virt = dma_alloc_coherent(&pdev->dev, size, &iova, GFP_KERNEL); + if (!virt) + return -ENOMEM; + + falcon->firmware.virt = virt; + falcon->firmware.iova = iova; + + err = falcon_load_firmware(falcon); + if (err < 0) + goto cleanup; + + return 0; + +cleanup: + dma_free_coherent(&pdev->dev, size, virt, iova); + + return err; +} + +/** + * Finalize DLA falcon power on + */ +int nvdla_flcn_finalize_poweron(struct platform_device *pdev) +{ + struct nvhost_device_data *pdata = platform_get_drvdata(pdev); +#ifdef CONFIG_IOMMU_API + struct iommu_fwspec *spec = dev_iommu_fwspec_get(&pdev->dev); +#endif + struct falcon *falcon; + int err; + u32 value; + + if (!pdata->falcon_data) { + err = nvdla_flcn_init(pdev, pdata); + if (err < 0) + return -ENOMEM; + } + + falcon = pdata->falcon_data; + + err = nvdla_flcn_load_firmware(pdev, falcon, pdata->firmware_name); + if (err < 0) + return err; + +#ifdef CONFIG_IOMMU_API + if (spec) { + host1x_writel(pdev, pdata->transcfg_addr, pdata->transcfg_val); + + if (spec->num_ids > 0) { + value = spec->ids[0] & 0xffff; + host1x_writel(pdev, THI_STREAMID0, value); + host1x_writel(pdev, THI_STREAMID1, value); + } + } +#endif + + err = falcon_boot(falcon); + if (err < 0) + return err; + + err = falcon_wait_idle(falcon); + if (err < 0) { + dev_err(&pdev->dev, "falcon boot timed out\n"); + return err; + } + + if (pdata->flcn_isr) + enable_irq(pdata->irq); + + return 0; +} \ No newline at end of file diff --git a/drivers/video/tegra/host/nvdla/port/fw/nvdla_falcon.h b/drivers/video/tegra/host/nvdla/port/fw/nvdla_falcon.h new file mode 100644 index 00000000..2520fa89 --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/fw/nvdla_falcon.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2022, NVIDIA Corporation. All rights reserved. + */ + +#ifndef _NVDLA_PORT_FW_FALCON_H_ +#define _NVDLA_PORT_FW_FALCON_H_ + +#include +#include + +/* Forward declaration for nvhost_device_data */ +struct nvhost_device_data; + +#define FALCON_UCLASS_METHOD_OFFSET 0x00000040 + +#define FALCON_UCLASS_METHOD_DATA 0x00000044 + +#define FALCON_IRQMSET 0x00001010 +#define FALCON_IRQMSET_WDTMR (1 << 1) +#define FALCON_IRQMSET_HALT (1 << 4) +#define FALCON_IRQMSET_EXTERR (1 << 5) +#define FALCON_IRQMSET_SWGEN0 (1 << 6) +#define FALCON_IRQMSET_SWGEN1 (1 << 7) +#define FALCON_IRQMSET_EXT(v) (((v) & 0xff) << 8) + +#define FALCON_IRQDEST 0x0000101c +#define FALCON_IRQDEST_HALT (1 << 4) +#define FALCON_IRQDEST_EXTERR (1 << 5) +#define FALCON_IRQDEST_SWGEN0 (1 << 6) +#define FALCON_IRQDEST_SWGEN1 (1 << 7) +#define FALCON_IRQDEST_EXT(v) (((v) & 0xff) << 8) + +#define FALCON_ITFEN 0x00001048 +#define FALCON_ITFEN_CTXEN (1 << 0) +#define FALCON_ITFEN_MTHDEN (1 << 1) + +#define FALCON_IDLESTATE 0x0000104c + +#define FALCON_CPUCTL 0x00001100 +#define FALCON_CPUCTL_STARTCPU (1 << 1) + +#define FALCON_BOOTVEC 0x00001104 + +#define FALCON_DMACTL 0x0000110c +#define FALCON_DMACTL_DMEM_SCRUBBING (1 << 1) +#define FALCON_DMACTL_IMEM_SCRUBBING (1 << 2) + +#define FALCON_DMATRFBASE 0x00001110 + +#define FALCON_DMATRFMOFFS 0x00001114 + +#define FALCON_DMATRFCMD 0x00001118 +#define FALCON_DMATRFCMD_IDLE (1 << 1) +#define FALCON_DMATRFCMD_IMEM (1 << 4) +#define FALCON_DMATRFCMD_SIZE_256B (6 << 8) + +#define FALCON_DMATRFFBOFFS 0x0000111c + +/* THI register definitions */ +#define THI_STREAMID0 0x00000030 +#define THI_STREAMID1 0x00000034 + +struct falcon_fw_bin_header_v1 { + u32 magic; /* 0x10de */ + u32 version; /* version of bin format (1) */ + u32 size; /* entire image size including this header */ + u32 os_header_offset; + u32 os_data_offset; + u32 os_size; +}; + +struct falcon_fw_os_app_v1 { + u32 offset; + u32 size; +}; + +struct falcon_fw_os_header_v1 { + u32 code_offset; + u32 code_size; + u32 data_offset; + u32 data_size; +}; + +struct falcon_firmware_section { + unsigned long offset; + size_t size; +}; + +struct falcon_firmware { + /* Firmware after it is read but not loaded */ + const struct firmware *firmware; + + /* Raw firmware data */ + dma_addr_t iova; + dma_addr_t phys; + void *virt; + size_t size; + + /* Parsed firmware information */ + struct falcon_firmware_section bin_data; + struct falcon_firmware_section data; + struct falcon_firmware_section code; +}; + +struct falcon { + /* Set by falcon client */ + struct device *dev; + void __iomem *regs; + + struct falcon_firmware firmware; +}; + +/* NVDLA Falcon API functions */ +int nvdla_flcn_intr_init(struct platform_device *pdev); +int nvdla_flcn_reload_fw(struct platform_device *pdev); +int nvdla_flcn_prepare_poweroff(struct platform_device *pdev); +int nvdla_flcn_finalize_poweron(struct platform_device *pdev); + +#endif /* _NVDLA_PORT_FW_FALCON_H_ */ \ No newline at end of file diff --git a/drivers/video/tegra/host/nvdla/port/fw/nvdla_fw_flcn.c b/drivers/video/tegra/host/nvdla/port/fw/nvdla_fw_flcn.c index f2b5f75b..eda1c652 100644 --- a/drivers/video/tegra/host/nvdla/port/fw/nvdla_fw_flcn.c +++ b/drivers/video/tegra/host/nvdla/port/fw/nvdla_fw_flcn.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +/* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * * NVDLA KMD-FALCON implementation */ @@ -12,6 +12,7 @@ #include "../../nvdla.h" #include "../../nvdla_debug.h" #include "../../nvdla_hw_flcn.h" +#include "nvdla_falcon.h" #include #include @@ -35,7 +36,7 @@ int32_t nvdla_fw_poweron(struct platform_device *pdev) pdata = platform_get_drvdata(pdev); nvdla_dev = pdata->private_data; - err = nvhost_flcn_finalize_poweron(pdev); + err = nvdla_flcn_finalize_poweron(pdev); if (err) { nvdla_dbg_err(pdev, "failed to poweron\n"); goto fail; @@ -69,7 +70,7 @@ int32_t nvdla_fw_poweron(struct platform_device *pdev) return 0; poweroff: - (void) nvhost_flcn_prepare_poweroff(pdev); + (void) nvdla_flcn_prepare_poweroff(pdev); fail: return err; } @@ -83,7 +84,7 @@ int32_t nvdla_fw_poweroff(struct platform_device *pdev) goto fail; } - err = nvhost_flcn_prepare_poweroff(pdev); + err = nvdla_flcn_prepare_poweroff(pdev); fail: return err; @@ -94,7 +95,7 @@ int32_t nvdla_fw_init(struct platform_device *pdev) struct nvhost_device_data *pdata = platform_get_drvdata(pdev); if (pdata->flcn_isr) - flcn_intr_init(pdev); + nvdla_flcn_intr_init(pdev); return 0; } @@ -106,7 +107,7 @@ void nvdla_fw_deinit(struct platform_device *pdev) int32_t nvdla_fw_reload(struct platform_device *pdev) { - return flcn_reload_fw(pdev); + return nvdla_flcn_reload_fw(pdev); } int32_t nvdla_fw_send_cmd(struct platform_device *pdev,