/* * Tegra flcn common driver * * Copyright (c) 2011-2018, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include /* for kzalloc */ #include /* for parsing ucode image wrt endianness */ #include /* for udelay */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dev.h" #include "class_ids.h" #include "bus_client.h" #include "nvhost_acm.h" #include "nvhost_vm.h" #include "nvhost_scale.h" #include "nvhost_channel.h" #include "flcn.h" #include "hw_flcn.h" #include "t124/hardware_t124.h" /* for nvhost opcodes*/ #include "t124/t124.h" #include "t210/t210.h" #ifdef CONFIG_ARCH_TEGRA_18x_SOC #include "t186/t186.h" #endif #ifdef CONFIG_TEGRA_T19X_GRHOST #include "t194/t194.h" #endif static int nvhost_flcn_init_sw(struct platform_device *dev); static int nvhost_flcn_deinit_sw(struct platform_device *dev); #define FLCN_IDLE_TIMEOUT_DEFAULT 100000 /* 100 milliseconds */ #define FLCN_IDLE_CHECK_PERIOD 10 /* 10 usec */ static irqreturn_t flcn_isr(int irq, void *dev_id) { struct platform_device *pdev = (struct platform_device *)(dev_id); struct nvhost_device_data *pdata = nvhost_get_devdata(pdev); unsigned long flags; spin_lock_irqsave(&pdata->mirq_lock, flags); if (pdata->flcn_isr) pdata->flcn_isr(pdev); spin_unlock_irqrestore(&pdata->mirq_lock, flags); return IRQ_HANDLED; } int flcn_intr_init(struct platform_device *pdev) { struct nvhost_device_data *pdata = nvhost_get_devdata(pdev); const char *dev_name; int ret = 0; if (!pdata->module_irq) return 0; pdata->irq = platform_get_irq(pdev, 0); if (pdata->irq < 0) { dev_err(&pdev->dev, "failed to get IRQ\n"); return -ENXIO; } spin_lock_init(&pdata->mirq_lock); dev_name = get_device_name_for_dev(pdev); ret = request_irq(pdata->irq, flcn_isr, 0, dev_name, 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; } static int nvhost_flcn_wait_idle(struct platform_device *pdev) { int ret; u32 val = 0; void __iomem *addr = get_aperture(pdev, 0) + flcn_idlestate_r(); nvhost_dbg_fn(""); ret = readl_poll_timeout(addr, val, (val == 0), FLCN_IDLE_CHECK_PERIOD, FLCN_IDLE_TIMEOUT_DEFAULT); if (ret) nvhost_err(&pdev->dev, "flcn_idle_state_r =%x\n", val); else nvhost_dbg_fn("done"); return ret; } static int nvhost_flcn_dma_wait_idle(struct platform_device *pdev) { int ret; void __iomem *addr = get_aperture(pdev, 0) + flcn_dmatrfcmd_r(); u32 val; nvhost_dbg_fn(""); ret = readl_poll_timeout(addr, val, (flcn_dmatrfcmd_idle_v(val) == flcn_dmatrfcmd_idle_true_v()), FLCN_IDLE_CHECK_PERIOD, FLCN_IDLE_TIMEOUT_DEFAULT); if (ret) nvhost_err(&pdev->dev, "flcn_idle_state_r =%x\n", val); else nvhost_dbg_fn("done"); return ret; } static int flcn_dma_pa_to_internal_256b(struct platform_device *pdev, phys_addr_t pa, u32 internal_offset, bool imem) { struct nvhost_device_data *pdata = nvhost_get_devdata(pdev); u32 cmd = flcn_dmatrfcmd_size_256b_f(); u32 pa_offset = flcn_dmatrffboffs_offs_f(pa); u32 i_offset = flcn_dmatrfmoffs_offs_f(internal_offset); if (imem) cmd |= flcn_dmatrfcmd_imem_true_f(); if (pdata->isolate_contexts) cmd |= flcn_dmatrfcmd_dmactx_f(1); host1x_writel(pdev, flcn_dmatrfmoffs_r(), i_offset); host1x_writel(pdev, flcn_dmatrffboffs_r(), pa_offset); host1x_writel(pdev, flcn_dmatrfcmd_r(), cmd); return nvhost_flcn_dma_wait_idle(pdev); } int nvhost_flcn_load_image(struct platform_device *pdev, dma_addr_t dma_addr, struct flcn_os_image *os, u32 imem_offset) { int ret = 0; u32 offset; host1x_writel(pdev, flcn_dmactl_r(), 0); host1x_writel(pdev, flcn_dmatrfbase_r(), (dma_addr + os->bin_data_offset) >> 8); /* Write ucode data to dmem */ dev_dbg(&pdev->dev, "flcn_boot: load dmem\n"); for (offset = 0; offset < os->data_size; offset += 256) { ret = flcn_dma_pa_to_internal_256b(pdev, os->data_offset + offset, offset, false); if (ret) goto err; } /* Write nvdec ucode to imem */ dev_dbg(&pdev->dev, "flcn_boot: load imem\n"); for (offset = imem_offset; offset < os->code_size; offset += 256) { ret = flcn_dma_pa_to_internal_256b(pdev, os->code_offset + offset, offset, true); if (ret) goto err; } err: if (ret) nvhost_err(&pdev->dev, "flcn_load_image failed: 0x%x\n", ret); return ret; } static int flcn_setup_ucode_fce(struct platform_device *dev, struct flcn *v, struct ucode_v1_flcn *ucode) { u32 *ucode_ptr = v->mapped; struct nvhost_device_data *pdata = platform_get_drvdata(dev); if (ucode->bin_header->fce_bin_header_offset == 0xa5a5a5a5) return 0; ucode->fce_header = (struct ucode_fce_header_v1_flcn *) (((void *)ucode_ptr) + ucode->bin_header->fce_bin_header_offset); nvhost_dbg_info("fce ucode header: offset, buffer_size, size: 0x%x 0x%x 0x%x", ucode->fce_header->fce_ucode_offset, ucode->fce_header->fce_ucode_buffer_size, ucode->fce_header->fce_ucode_size); /* if isolation is enabled.. */ if (pdata->isolate_contexts) { /* create and map fce shadow to all contexts */ v->fce_mapped = nvhost_vm_allocate_firmware_area(dev, ucode->fce_header->fce_ucode_size, &v->fce_dma_addr); if (!v->fce_mapped) return -ENOMEM; memcpy(v->fce_mapped, (u8 *)v->mapped + ucode->bin_header->fce_bin_data_offset, ucode->fce_header->fce_ucode_size); } else { /* ..otherwise use the one in firmware image */ v->fce_dma_addr = v->dma_addr + ucode->bin_header->fce_bin_data_offset; } v->fce.size = ucode->fce_header->fce_ucode_size; v->fce.data_offset = ucode->bin_header->fce_bin_data_offset; return 0; } int flcn_setup_ucode_image(struct platform_device *dev, struct flcn *v, const struct firmware *ucode_fw, struct ucode_v1_flcn *ucode) { int w; u32 *ucode_ptr = v->mapped; nvhost_dbg_fn(""); /* image data is little endian. */ /* copy the whole thing taking into account endianness */ for (w = 0; w < ucode_fw->size/sizeof(u32); w++) ucode_ptr[w] = le32_to_cpu(((__le32 *)ucode_fw->data)[w]); ucode->bin_header = (struct ucode_bin_header_v1_flcn *)ucode_ptr; /* endian problems would show up right here */ if (ucode->bin_header->bin_magic != 0x10de && ucode->bin_header->bin_magic != 0x10fe) { dev_err(&dev->dev, "failed to get firmware magic"); return -EINVAL; } if (ucode->bin_header->bin_ver != 1) { dev_err(&dev->dev, "unsupported firmware version"); return -ENOENT; } /* shouldn't be bigger than what firmware thinks */ if (ucode->bin_header->bin_size > ucode_fw->size) { dev_err(&dev->dev, "ucode image size inconsistency"); return -EINVAL; } nvhost_dbg_info("ucode bin header: magic:0x%x ver:%d size:%d", ucode->bin_header->bin_magic, ucode->bin_header->bin_ver, ucode->bin_header->bin_size); nvhost_dbg_info("ucode bin header: os bin (header,data) offset size: 0x%x, 0x%x %d", ucode->bin_header->os_bin_header_offset, ucode->bin_header->os_bin_data_offset, ucode->bin_header->os_bin_size); nvhost_dbg_info("ucode bin header: fce bin (header,data) offset size: 0x%x, 0x%x %d", ucode->bin_header->fce_bin_header_offset, ucode->bin_header->fce_bin_data_offset, ucode->bin_header->fce_bin_size); ucode->os_header = (struct ucode_os_header_v1_flcn *) (((void *)ucode_ptr) + ucode->bin_header->os_bin_header_offset); nvhost_dbg_info("os ucode header: os code (offset,size): 0x%x, 0x%x", ucode->os_header->os_code_offset, ucode->os_header->os_code_size); nvhost_dbg_info("os ucode header: os data (offset,size): 0x%x, 0x%x", ucode->os_header->os_data_offset, ucode->os_header->os_data_size); nvhost_dbg_info("os ucode header: num apps: %d", ucode->os_header->num_apps); v->os.size = ucode->bin_header->os_bin_size; v->os.bin_data_offset = ucode->bin_header->os_bin_data_offset; v->os.code_offset = ucode->os_header->os_code_offset; v->os.data_offset = ucode->os_header->os_data_offset; v->os.data_size = ucode->os_header->os_data_size; v->os.code_size = ucode->os_header->os_code_size; v->os.bin_magic = ucode->bin_header->bin_magic; v->os.bin_ver_tag = ucode->bin_header->bin_ver_tag; return 0; } int flcn_reload_fw(struct platform_device *pdev) { int err; struct device *device = &pdev->dev; err = nvhost_module_do_idle(device); if (err) return err; nvhost_flcn_deinit_sw(pdev); err = nvhost_module_do_unidle(device); if (err) return err; return 0; } static int flcn_read_ucode(struct platform_device *dev, const char *fw_name, struct flcn *v) { const struct firmware *ucode_fw; struct ucode_v1_flcn ucode; int err; DEFINE_DMA_ATTRS(attrs); nvhost_dbg_fn(""); dma_set_attr(DMA_ATTR_READ_ONLY, __DMA_ATTR(attrs)); v->dma_addr = 0; v->mapped = NULL; ucode_fw = nvhost_client_request_firmware(dev, fw_name); if (!ucode_fw) { nvhost_dbg_fn("request firmware failed"); dev_err(&dev->dev, "failed to get firmware\n"); err = -ENOENT; return err; } v->size = ucode_fw->size; v->mapped = dma_alloc_attrs(&dev->dev, v->size, &v->dma_addr, GFP_KERNEL, __DMA_ATTR(attrs)); if (!v->mapped) { dev_err(&dev->dev, "dma memory allocation failed"); err = -ENOMEM; goto clean_up; } err = flcn_setup_ucode_image(dev, v, ucode_fw, &ucode); if (err) { dev_err(&dev->dev, "failed to parse firmware image\n"); goto clean_up; } err = flcn_setup_ucode_fce(dev, v, &ucode); if (err) { dev_err(&dev->dev, "failed to parse fce image\n"); goto clean_up; } v->valid = true; release_firmware(ucode_fw); return 0; clean_up: if (v->mapped) { dma_free_attrs(&dev->dev, v->size, v->mapped, v->dma_addr, __DMA_ATTR(attrs)); v->mapped = NULL; v->dma_addr = 0; } release_firmware(ucode_fw); return err; } int nvhost_flcn_wait_mem_scrubbing(struct platform_device *dev) { int retries = FLCN_IDLE_TIMEOUT_DEFAULT / FLCN_IDLE_CHECK_PERIOD; nvhost_dbg_fn(""); do { u32 w = host1x_readl(dev, flcn_dmactl_r()) & (flcn_dmactl_dmem_scrubbing_m() | flcn_dmactl_imem_scrubbing_m()); if (!w) { nvhost_dbg_fn("done"); return 0; } udelay(FLCN_IDLE_CHECK_PERIOD); } while (--retries || !tegra_platform_is_silicon()); nvhost_err(&dev->dev, "Falcon mem scrubbing timeout"); return -ETIMEDOUT; } int nvhost_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; } void nvhost_flcn_irq_mask_set(struct platform_device *pdev) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); /* setup falcon interrupts and enable interface */ if (!pdata->self_config_flcn_isr) host1x_writel(pdev, flcn_irqmset_r(), (flcn_irqmset_ext_f(0xff) | flcn_irqmset_swgen1_set_f() | flcn_irqmset_swgen0_set_f() | flcn_irqmset_exterr_set_f() | flcn_irqmset_halt_set_f() | flcn_irqmset_wdtmr_set_f())); } void nvhost_flcn_irq_dest_set(struct platform_device *pdev) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); if (!pdata->self_config_flcn_isr) host1x_writel(pdev, flcn_irqdest_r(), (flcn_irqdest_host_ext_f(0xff) | flcn_irqdest_host_swgen1_host_f() | flcn_irqdest_host_swgen0_host_f() | flcn_irqdest_host_exterr_host_f() | flcn_irqdest_host_halt_host_f())); } void nvhost_flcn_ctxtsw_init(struct platform_device *pdev) { host1x_writel(pdev, flcn_itfen_r(), (flcn_itfen_mthden_enable_f() | flcn_itfen_ctxen_enable_f())); } int nvhost_flcn_start(struct platform_device *pdev, u32 bootvec) { int err = 0; /* boot falcon */ dev_dbg(&pdev->dev, "flcn_boot: start falcon\n"); host1x_writel(pdev, flcn_bootvec_r(), flcn_bootvec_vec_f(bootvec)); host1x_writel(pdev, flcn_cpuctl_r(), flcn_cpuctl_startcpu_true_f()); err = nvhost_flcn_wait_idle(pdev); if (err != 0) nvhost_err(&pdev->dev, "boot failed due to timeout"); return err; } int nvhost_flcn_finalize_poweron(struct platform_device *pdev) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); struct flcn *v; int err = 0; err = nvhost_flcn_init_sw(pdev); if (err) return err; v = get_flcn(pdev); err = nvhost_flcn_wait_mem_scrubbing(pdev); if (err) return err; /* load transcfg configuration if defined */ if (pdata->transcfg_addr) host1x_writel(pdev, pdata->transcfg_addr, pdata->transcfg_val); err = nvhost_flcn_load_image(pdev, v->dma_addr, &v->os, 0); if (err) return err; nvhost_flcn_irq_mask_set(pdev); nvhost_flcn_irq_dest_set(pdev); if (pdata->flcn_isr) enable_irq(pdata->irq); nvhost_flcn_ctxtsw_init(pdev); err = nvhost_flcn_start(pdev, 0); return err; } int nvhost_flcn_common_isr(struct platform_device *pdev) { u32 irqstat, exci, mailbox0, mailbox1; irqstat = host1x_readl(pdev, flcn_irqstat_r()); exci = host1x_readl(pdev, flcn_exci_r()); mailbox0 = host1x_readl(pdev, flcn_mailbox0_r()); mailbox1 = host1x_readl(pdev, flcn_mailbox1_r()); dev_err(&pdev->dev, "irqstat: %08x, exci: %08x, mailbox0: %08x, mailbox1: %08x", irqstat, exci, mailbox0, mailbox1); /* logic to clear the interrupt */ host1x_writel(pdev, flcn_thi_int_stat_r(), flcn_thi_int_stat_clr_f()); host1x_readl(pdev, flcn_thi_int_stat_r()); host1x_writel(pdev, flcn_irqsclr_r(), flcn_irqsclr_swgen0_set_f()); return 0; } static int nvhost_flcn_init_sw(struct platform_device *dev) { int err = 0; struct nvhost_device_data *pdata = nvhost_get_devdata(dev); struct flcn *v = get_flcn(dev); nvhost_dbg_fn("in dev:%p v:%p", dev, v); if (v) return 0; v = kzalloc(sizeof(*v), GFP_KERNEL); if (!v) { err = -ENOMEM; goto clean_up; } set_flcn(dev, v); nvhost_dbg_fn("primed dev:%p v:%p", dev, v); err = flcn_read_ucode(dev, pdata->firmware_name, v); if (err || !v->valid) goto clean_up; return 0; clean_up: nvhost_err(&dev->dev, "failed : 0x%x", err); return err; } static int nvhost_flcn_deinit_sw(struct platform_device *dev) { struct flcn *v = get_flcn(dev); #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) DEFINE_DMA_ATTRS(attrs); dma_set_attr(DMA_ATTR_READ_ONLY, &attrs); #endif if (!v) return 0; if (v->mapped) { #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) dma_free_attrs(&dev->dev, v->size, v->mapped, v->dma_addr, &attrs); #else dma_free_attrs(&dev->dev, v->size, v->mapped, v->dma_addr, DMA_ATTR_READ_ONLY); #endif v->mapped = NULL; v->dma_addr = 0; } kfree(v); set_flcn(dev, NULL); return 0; } int nvhost_vic_finalize_poweron(struct platform_device *pdev) { struct flcn *v; int err; err = nvhost_flcn_finalize_poweron(pdev); if (err) return err; v = get_flcn(pdev); host1x_writel(pdev, FLCN_UCLASS_METHOD_OFFSET * 4, NVA0B6_VIDEO_COMPOSITOR_SET_APPLICATION_ID >> 2); host1x_writel(pdev, FLCN_UCLASS_METHOD_DATA * 4, 1); host1x_writel(pdev, FLCN_UCLASS_METHOD_OFFSET * 4, NVA0B6_VIDEO_COMPOSITOR_SET_FCE_UCODE_SIZE >> 2); host1x_writel(pdev, FLCN_UCLASS_METHOD_DATA * 4, v->fce.size); host1x_writel(pdev, FLCN_UCLASS_METHOD_OFFSET * 4, NVA0B6_VIDEO_COMPOSITOR_SET_FCE_UCODE_OFFSET >> 2); host1x_writel(pdev, FLCN_UCLASS_METHOD_DATA * 4, (v->dma_addr + v->fce.data_offset) >> 8); return 0; } int nvhost_vic_init_context(struct platform_device *pdev, struct nvhost_cdma *cdma) { struct flcn *v; int err; err = nvhost_flcn_init_sw(pdev); if (err) return err; v = get_flcn(pdev); /* load application id */ nvhost_cdma_push(cdma, nvhost_opcode_setclass(NV_GRAPHICS_VIC_CLASS_ID, FLCN_UCLASS_METHOD_OFFSET, 1), NVA0B6_VIDEO_COMPOSITOR_SET_APPLICATION_ID >> 2); nvhost_cdma_push(cdma, nvhost_opcode_setclass(NV_GRAPHICS_VIC_CLASS_ID, FLCN_UCLASS_METHOD_DATA, 1), 1); /* set fce ucode size */ nvhost_cdma_push(cdma, nvhost_opcode_setclass(NV_GRAPHICS_VIC_CLASS_ID, FLCN_UCLASS_METHOD_OFFSET, 1), NVA0B6_VIDEO_COMPOSITOR_SET_FCE_UCODE_SIZE >> 2); nvhost_cdma_push(cdma, nvhost_opcode_setclass(NV_GRAPHICS_VIC_CLASS_ID, FLCN_UCLASS_METHOD_DATA, 1), v->fce.size); /* set fce ucode offset */ nvhost_cdma_push(cdma, nvhost_opcode_setclass(NV_GRAPHICS_VIC_CLASS_ID, FLCN_UCLASS_METHOD_OFFSET, 1), NVA0B6_VIDEO_COMPOSITOR_SET_FCE_UCODE_OFFSET >> 2); nvhost_cdma_push(cdma, nvhost_opcode_setclass(NV_GRAPHICS_VIC_CLASS_ID, FLCN_UCLASS_METHOD_DATA, 1), v->fce_dma_addr >> 8); return 0; } void flcn_enable_timestamps(struct platform_device *pdev, struct nvhost_cdma *cdma, dma_addr_t timestamp_addr) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); /* set timestamp buffer offset */ nvhost_cdma_push(cdma, nvhost_opcode_setclass(pdata->class, FLCN_UCLASS_METHOD_OFFSET, 1), FLCN_UCLASS_METHOD_ADDR_TSP); nvhost_cdma_push(cdma, nvhost_opcode_setclass(pdata->class, FLCN_UCLASS_METHOD_DATA, 1), timestamp_addr >> 8); } int nvhost_vic_aggregate_constraints(struct platform_device *dev, int clk_index, unsigned long floor_rate, unsigned long pixelrate, unsigned long bw_constraint) { struct nvhost_device_data *pdata = nvhost_get_devdata(dev); if (!pdata) { dev_err(&dev->dev, "no platform data\n"); /* return 0 to fall-back on default policy */ return 0; } /* Fall-back to default policy if pixelrate * is unavailable or clk index is incorrect. * Here clk_index 2 is for floor client. */ if (!pixelrate || clk_index != 2) return 0; /* Compute VIC frequency based on pixelrate */ return pixelrate / pdata->num_ppc; } static struct of_device_id tegra_flcn_of_match[] = { #ifdef CONFIG_TEGRA_GRHOST_VIC { .compatible = "nvidia,tegra124-vic", .data = (struct nvhost_device_data *)&t124_vic_info }, { .compatible = "nvidia,tegra210-vic", .data = (struct nvhost_device_data *)&t21_vic_info }, #endif #if defined(CONFIG_TEGRA_GRHOST_NVENC) { .compatible = "nvidia,tegra124-msenc", .data = (struct nvhost_device_data *)&t124_msenc_info }, #endif #ifdef TEGRA_21X_OR_HIGHER_CONFIG #if defined(CONFIG_TEGRA_GRHOST_NVENC) { .compatible = "nvidia,tegra210-nvenc", .data = (struct nvhost_device_data *)&t21_msenc_info }, #endif #endif #ifdef TEGRA_21X_OR_HIGHER_CONFIG { .compatible = "nvidia,tegra210-nvjpg", .data = (struct nvhost_device_data *)&t21_nvjpg_info }, #endif #ifdef CONFIG_ARCH_TEGRA_18x_SOC #if defined(CONFIG_TEGRA_GRHOST_VIC) { .compatible = "nvidia,tegra186-vic", .data = (struct nvhost_device_data *)&t18_vic_info }, #endif #if defined(CONFIG_TEGRA_GRHOST_NVJPG) { .compatible = "nvidia,tegra186-nvjpg", .data = (struct nvhost_device_data *)&t18_nvjpg_info }, #endif #if defined(CONFIG_TEGRA_GRHOST_NVENC) { .compatible = "nvidia,tegra186-nvenc", .data = (struct nvhost_device_data *)&t18_msenc_info }, #endif #endif #ifdef CONFIG_TEGRA_T19X_GRHOST #if defined(CONFIG_TEGRA_GRHOST_VIC) { .compatible = "nvidia,tegra194-vic", .data = (struct nvhost_device_data *)&t19_vic_info }, #endif #if defined(CONFIG_TEGRA_GRHOST_NVJPG) { .compatible = "nvidia,tegra194-nvjpg", .data = (struct nvhost_device_data *)&t19_nvjpg_info }, #endif #if defined(CONFIG_TEGRA_GRHOST_NVENC) { .compatible = "nvidia,tegra194-nvenc", .data = (struct nvhost_device_data *)&t19_msenc_info, .name = "nvenc" }, { .compatible = "nvidia,tegra194-nvenc", .data = (struct nvhost_device_data *)&t19_nvenc1_info, .name = "nvenc1" }, #endif #endif { }, }; static ssize_t reload_fw_write(struct device *device, struct device_attribute *attr, const char *buf, size_t count) { int err; unsigned long val = 0; struct platform_device *pdev = to_platform_device(device); if (kstrtoul(buf, 0, &val) < 0) return -EINVAL; if (!val) return -EINVAL; err = flcn_reload_fw(pdev); if (err) return err; return count; } static DEVICE_ATTR(reload_fw, 0200, NULL, reload_fw_write); static int flcn_probe(struct platform_device *dev) { int err; struct nvhost_device_data *pdata = NULL; if (dev->dev.of_node) { const struct of_device_id *match; match = of_match_device(tegra_flcn_of_match, &dev->dev); if (match) pdata = (struct nvhost_device_data *)match->data; } else pdata = (struct nvhost_device_data *)dev->dev.platform_data; if (!pdata) { dev_err(&dev->dev, "no platform data\n"); return -ENODATA; } nvhost_dbg_fn("dev:%p pdata:%p", dev, pdata); pdata->pdev = dev; mutex_init(&pdata->lock); platform_set_drvdata(dev, pdata); err = device_create_file(&dev->dev, &dev_attr_reload_fw); if (err) return err; err = nvhost_client_device_get_resources(dev); if (err) return err; dev->dev.platform_data = NULL; nvhost_module_init(dev); err = nvhost_client_device_init(dev); if (err) { nvhost_dbg_fn("failed to init client device for %s", dev->name); pm_runtime_put(&dev->dev); return err; } if (pdata->flcn_isr) flcn_intr_init(dev); return 0; } static int __exit flcn_remove(struct platform_device *pdev) { nvhost_client_device_release(pdev); return 0; } static struct platform_device_id flcn_id_table[] = { { .name = "vic03" }, { .name = "msenc" }, { .name = "msenc" }, { .name = "nvjpg" }, {}, }; static struct platform_driver flcn_driver = { .probe = flcn_probe, .remove = __exit_p(flcn_remove), .driver = { .owner = THIS_MODULE, .name = "falcon", #ifdef CONFIG_OF .of_match_table = tegra_flcn_of_match, #endif #ifdef CONFIG_PM .pm = &nvhost_module_pm_ops, #endif .suppress_bind_attrs = true, }, .id_table = flcn_id_table, }; static struct of_device_id tegra_flcn_domain_match[] = { #ifdef CONFIG_TEGRA_GRHOST_VIC {.compatible = "nvidia,tegra124-vic03-pd", .data = (struct nvhost_device_data *)&t124_vic_info}, {.compatible = "nvidia,tegra132-vic03-pd", .data = (struct nvhost_device_data *)&t124_vic_info}, #endif #if defined(CONFIG_TEGRA_GRHOST_NVENC) {.compatible = "nvidia,tegra124-msenc-pd", .data = (struct nvhost_device_data *)&t124_msenc_info}, {.compatible = "nvidia,tegra132-msenc-pd", .data = (struct nvhost_device_data *)&t124_msenc_info}, #endif #ifdef CONFIG_TEGRA_GRHOST_VIC {.compatible = "nvidia,tegra210-vic03-pd", .data = (struct nvhost_device_data *)&t21_vic_info}, #endif #ifdef TEGRA_21X_OR_HIGHER_CONFIG #if defined(CONFIG_TEGRA_GRHOST_NVENC) {.compatible = "nvidia,tegra210-msenc-pd", .data = (struct nvhost_device_data *)&t21_msenc_info}, #endif #if defined(CONFIG_TEGRA_GRHOST_NVJPG) {.compatible = "nvidia,tegra210-nvjpg-pd", .data = (struct nvhost_device_data *)&t21_nvjpg_info}, #endif #endif #ifdef CONFIG_ARCH_TEGRA_18x_SOC #if defined(CONFIG_TEGRA_GRHOST_VIC) {.compatible = "nvidia,tegra186-vic03-pd", .data = (struct nvhost_device_data *)&t18_vic_info}, #endif #if defined(CONFIG_TEGRA_GRHOST_NVENC) {.compatible = "nvidia,tegra186-msenc-pd", .data = (struct nvhost_device_data *)&t18_msenc_info}, #endif #if defined(CONFIG_TEGRA_GRHOST_NVJPG) {.compatible = "nvidia,tegra186-nvjpg-pd", .data = (struct nvhost_device_data *)&t18_nvjpg_info}, #endif #endif #ifdef CONFIG_TEGRA_T19X_GRHOST #if defined(CONFIG_TEGRA_GRHOST_NVENC) {.compatible = "nvidia,tegra194-nvenc1-pd", .data = (struct nvhost_device_data *)&t19_nvenc1_info}, #endif #endif {}, }; static int __init flcn_init(void) { int ret; ret = nvhost_domain_init(tegra_flcn_domain_match); if (ret) return ret; return platform_driver_register(&flcn_driver); } static void __exit flcn_exit(void) { platform_driver_unregister(&flcn_driver); } module_init(flcn_init); module_exit(flcn_exit);