// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include /* internal headers */ #include "vpu_cfg.h" #include "apusys_power.h" #include "apusys_core.h" #include "vpu_cfg.h" #include "vpu_power.h" #include "vpu_cmd.h" #include "vpu_cmn.h" #include "vpu_mem.h" #include "vpu_algo.h" #include "vpu_debug.h" #include "vpu_trace.h" #include "vpu_ioctl.h" #include "vpu_met.h" #include "vpu_tag.h" #include "vpu_hw.h" #include "apu.h" #include "apu_config.h" struct vpu_platform vpu_plat_mt6885; static void vpu_exit_drv_plat(void); static int vpu_suspend(struct vpu_device *vd); static int vpu_resume(struct vpu_device *vd); #define VPU_NUM_MAX 3 struct vpu_probe_info probe_info[VPU_NUM_MAX]; int vpu_set_init_info(struct mtk_apu *apu) { int i; struct vpu_probe_info *p; struct platform_device *pdev; struct vpu_init_info *info; struct vpu_device *vd = NULL; struct vpu_misc *misc; if (!apu || !apu->conf_buf) { vpu_cmd_debug("invalid argument: apu or conf_buf\n"); return -EINVAL; } info = (struct vpu_init_info *) get_apu_config_user_ptr(apu->conf_buf, eVPU_INIT_INFO); for (i = 0; i < vpu_drv->vp->core_num; i++) { p = &probe_info[i]; if (!p->np) { vpu_cmd_debug("probe_info %d invalid\n", i); return -EPROBE_DEFER; } if (p->bound) continue; pdev = of_find_device_by_node(p->np); if (!pdev) { vpu_cmd_debug("vpu pdev%d failed\n", i); return -EINVAL; } vd = (struct vpu_device *)platform_get_drvdata(pdev); info->algo_info_ptr[2 * i] = vd->iova_algo_info.m.pa; info->algo_info_ptr[2 * i + 1] = vd->iova_preload_info.m.pa; info->rst_vec[i] = vd->iova_reset.addr; info->dmem_addr[i] = vd->dmem.res->start; info->imem_addr[i] = vd->imem.res->start; info->iram_addr[i] = vd->iova_iram.addr; info->cmd_addr[i] = vd->iova_cmd.m.pa; info->log_addr[i] = vd->iova_work.m.pa; info->log_size[i] = vd->iova_work.m.length; p->bound = 1; } info->vpu_num = i; if (!vd) { vpu_cmd_debug("vd init failed\n"); return -EINVAL; } if (!info->cfg_addr && info->vpu_num) { misc = (void *)vpu_drv->iova_cfg.m.va; memset(misc, 0, sizeof(*misc)); misc->ulog_lv = 0x3; misc->pwr_off_delay = 3000; vd_mops(vd)->sync_for_device(vd->dev, &vpu_drv->iova_cfg); info->cfg_addr = vpu_drv->iova_cfg.addr; info->cfg_size = vpu_drv->iova_cfg.size; } return 0; } EXPORT_SYMBOL(vpu_set_init_info); int vpu_init_info_remove(struct mtk_apu *apu) { int i; struct vpu_probe_info *p; for (i = 0; i < VPU_NUM_MAX; i++) { p = &probe_info[i]; p->bound = 0; } return 0; } EXPORT_SYMBOL(vpu_init_info_remove); /* handle different user query */ static int vpu_ucmd_handle(struct vpu_device *vd, struct apusys_usercmd_hnd *ucmd) { int ret = 0; struct __vpu_algo *alg = NULL; struct list_head *ptr = NULL; struct list_head *tmp = NULL; struct vpu_uget_algo *palg_cmd = NULL; if (!ucmd->kva || !ucmd->size) { vpu_cmd_debug("%s: invalid argument(0x%llx/%u)\n", __func__, (uint64_t)ucmd->kva, ucmd->size); return -EINVAL; } /* Handling VPU_UCMD_GET_ALGO */ if (VPU_UCMD_CHECK(ucmd->kva, GET_ALGO)) { /* casting user cmd as struct vpu_uget_algo */ palg_cmd = (struct vpu_uget_algo *)ucmd->kva; mutex_lock(&vpu_drv->lock); /* looping each vpu_device for this algo */ list_for_each_safe(ptr, tmp, &vpu_drv->devs) { vd = list_entry(ptr, struct vpu_device, list); /* Normal Algo */ alg = vd->aln.ops->get(&vd->aln, palg_cmd->name, NULL); if (alg) { vd->aln.ops->put(alg); goto next; } /* Preload Algo */ alg = vd->alp.ops->get(&vd->alp, palg_cmd->name, NULL); if (alg) { vd->alp.ops->put(alg); goto next; } vpu_cmd_debug("%s: vpu%d: algo \"%s\" was not found\n", __func__, vd->id, palg_cmd->name); ret = -ENOENT; next: if (ret) break; } mutex_unlock(&vpu_drv->lock); } else { vpu_cmd_debug("%s: unknown user cmd: 0x%x\n", __func__, *(uint32_t *)ucmd->kva); ret = -EINVAL; } return ret; } static int vpu_req_check(struct apusys_cmd_handle *cmd) { int ret = 0; uint64_t mask = 0; struct apusys_cmdbuf *cmd_buf; struct vpu_request *req; if (cmd->num_cmdbufs != VPU_CMD_BUF_NUM) { pr_info("%s: invalid number 0x%x of vpu request\n", __func__, cmd->num_cmdbufs); return -EINVAL; } cmd_buf = &cmd->cmdbufs[0]; if (!cmd_buf->kva) { pr_info("%s: invalid kva %p of vpu request\n", __func__, cmd_buf->kva); return -EINVAL; } if (cmd_buf->size != sizeof(struct vpu_request)) { pr_info("%s: invalid size 0x%x of vpu request\n", __func__, cmd_buf->size); return -EINVAL; } req = (struct vpu_request *)cmd_buf->kva; mask = ~(VPU_REQ_F_ALG_RELOAD | VPU_REQ_F_ALG_CUSTOM | VPU_REQ_F_ALG_PRELOAD | VPU_REQ_F_PREEMPT_TEST); if (req->flags & mask) { pr_info("%s: invalid flags 0x%llx of vpu request\n", __func__, req->flags); return -EINVAL; } if (req->buffer_count > VPU_MAX_NUM_PORTS) { pr_info("%s: invalid buffer_count 0x%x of vpu request\n", __func__, req->buffer_count); return -EINVAL; } return ret; } int vpu_send_cmd_rt(int op, void *hnd, struct apusys_device *adev) { int ret = 0; struct vpu_device *vd; struct vpu_request *req; struct apusys_cmd_handle *cmd; vd = (struct vpu_device *)adev->private; switch (op) { case APUSYS_CMD_POWERON: case APUSYS_CMD_POWERDOWN: case APUSYS_CMD_FIRMWARE: case APUSYS_CMD_USER: vpu_cmd_debug("%s: vpu%d: operation not allowed: %d\n", __func__, vd->id, op); return -EACCES; case APUSYS_CMD_RESUME: case APUSYS_CMD_SUSPEND: return 0; case APUSYS_CMD_EXECUTE: cmd = (struct apusys_cmd_handle *)hnd; if (vpu_req_check(cmd)) return -EINVAL; req = (struct vpu_request *)cmd->cmdbufs[0].kva; vpu_trace_begin("vpu_%d|%s|cmd execute kid: 0x%08llx", vd->id, __func__, cmd->kid); vpu_cmd_debug("%s: vpu%d: EXECUTE, kva: %p kid: 0x%llx subcmd_idx: 0x%x\n", __func__, vd->id, (unsigned long)cmd->cmdbufs[0].kva, cmd->kid, cmd->subcmd_idx); /* overwrite vpu_req->boost from apusys_cmd */ req->power_param.boost_value = cmd->boost; VPU_REQ_FLAG_SET(req, ALG_PRELOAD); ret = vpu_preempt(vd, req); vpu_trace_end("vpu_%d|%s|cmd execute kid: 0x%08llx", vd->id, __func__, cmd->kid); /* report vpu_req exe time to apusy_cmd */ cmd->ip_time = req->busy_time / 1000; return ret; default: vpu_cmd_debug("%s: vpu%d: unknown command: %d\n", __func__, vd->id, op); break; } return -EINVAL; } /* interface to APUSYS */ int vpu_send_cmd(int op, void *hnd, struct apusys_device *adev) { int ret = 0; struct vpu_device *vd; struct vpu_request *req; struct apusys_power_hnd *pw; struct apusys_usercmd_hnd *ucmd; struct apusys_cmd_handle *cmd; vd = (struct vpu_device *)adev->private; switch (op) { case APUSYS_CMD_POWERON: pw = (struct apusys_power_hnd *)hnd; vpu_cmd_debug("%s: vpu%d: POWERON, boost: %d, opp: %d, timeout: %d\n", __func__, vd->id, pw->boost_val, pw->opp, pw->timeout); return vpu_pwr_up(vd, pw->boost_val, (pw->timeout ? vd->pw_off_latency : 0)); case APUSYS_CMD_POWERDOWN: vpu_cmd_debug("%s: vpu%d: POWERDOWN\n", __func__, vd->id); vpu_pwr_down(vd); return 0; case APUSYS_CMD_RESUME: vpu_cmd_debug("%s: vpu%d: RESUME\n", __func__, vd->id); return vpu_resume(vd); case APUSYS_CMD_SUSPEND: vpu_cmd_debug("%s: vpu%d: SUSPEND\n", __func__, vd->id); return vpu_suspend(vd); case APUSYS_CMD_EXECUTE: cmd = (struct apusys_cmd_handle *)hnd; if (vpu_req_check(cmd)) return -EINVAL; req = (struct vpu_request *)cmd->cmdbufs[0].kva; vpu_trace_begin("vpu_%d|%s|cmd execute kid: 0x%08llx", vd->id, __func__, cmd->kid); vpu_cmd_debug("%s: vpu%d: EXECUTE, kva: %p kid: 0x%llx subcmd_idx: 0x%x\n", __func__, vd->id, (unsigned long)cmd->cmdbufs[0].kva, cmd->kid, cmd->subcmd_idx); req->prio = 0; /* overwrite vpu_req->boost from apusys_cmd */ req->power_param.boost_value = cmd->boost; if (VPU_REQ_FLAG_TST(req, ALG_PRELOAD)) ret = vpu_preempt(vd, req); else ret = vpu_execute(vd, req); vpu_trace_end("vpu_%d|%s|cmd execute kid: 0x%08llx", vd->id, __func__, cmd->kid); /* report vpu_req exe time to apusy_cmd */ cmd->ip_time = req->busy_time / 1000; return ret; case APUSYS_CMD_PREEMPT: vpu_cmd_debug("%s: vpu%d: operation not allowed: %d\n", __func__, vd->id, op); return -EACCES; case APUSYS_CMD_USER: ucmd = (struct apusys_usercmd_hnd *)hnd; vpu_cmd_debug("%s: vpu%d: USER, op: 0x%x size %d\n", __func__, vd->id, *(uint32_t *)ucmd->kva, ucmd->size); return vpu_ucmd_handle(vd, ucmd); default: vpu_cmd_debug("%s: vpu%d: unknown command: %d\n", __func__, vd->id, op); break; } return -EINVAL; } /** * vpu_kbuf_alloc() - Allocate VPU kernel (execution) buffer * @vd: vpu device * * vd->lock and vpu_cmd_lock() must be locked before calling this function */ int vpu_kbuf_alloc(struct vpu_device *vd) { dma_addr_t iova = 0; struct timespec64 start, end; uint64_t period; struct vpu_mem_ops *mops = vd_mops(vd); if (vd->iova_kernel.m.va) return 0; ktime_get_ts64(&start); iova = mops->alloc(vd->dev, &vd->iova_kernel); ktime_get_ts64(&end); if (!iova) { pr_info("%s: vpu%d failed\n", __func__, vd->id); return -ENOMEM; } period = ((uint64_t)(timespec64_to_ns(&end) - timespec64_to_ns(&start))); mops->sync_for_cpu(vd->dev, &vd->iova_kernel); vpu_pwr_debug("%s: vpu%d, iova: 0x%llx, period: %llu ns, page num: %d\n", __func__, vd->id, (u64)iova, period, vd->iova_kernel.sgt.nents); return 0; } /** * vpu_kbuf_free() - Free VPU kernel (execution) buffer * @vd: vpu device * * vd->lock and vpu_cmd_lock() must be locked before calling this function */ int vpu_kbuf_free(struct vpu_device *vd) { struct platform_device *pdev = container_of(vd->dev, struct platform_device, dev); if (!vd->iova_kernel.m.va) return 0; vd_mops(vd)->free(&pdev->dev, &vd->iova_kernel); vd->iova_kernel.m.va = 0; return 0; } static struct vpu_device *vpu_alloc(struct platform_device *pdev) { struct vpu_device *vd; vd = kzalloc(sizeof(struct vpu_device), GFP_KERNEL); if (!vd) return NULL; vd->dev = &pdev->dev; platform_set_drvdata(pdev, vd); return vd; } static void vpu_free(struct platform_device *pdev) { struct vpu_device *vd = platform_get_drvdata(pdev); kfree(vd); platform_set_drvdata(pdev, NULL); } struct vpu_driver *vpu_drv; static void vpu_drv_release(struct kref *ref); static void vpu_drv_put(void) { if (!vpu_drv) return; kref_put(&vpu_drv->ref, vpu_drv_release); } static void vpu_drv_get(void) { kref_get(&vpu_drv->ref); } static void vpu_dev_add(struct vpu_device *vd) { /* add to vd list */ mutex_lock(&vpu_drv->lock); vpu_drv_get(); list_add_tail(&vd->list, &vpu_drv->devs); mutex_unlock(&vpu_drv->lock); } static void vpu_dev_del(struct vpu_device *vd) { /* remove from vd list */ mutex_lock(&vpu_drv->lock); list_del(&vd->list); mutex_unlock(&vpu_drv->lock); vpu_drv_put(); } static int vpu_init_bin(struct device_node *node) { uint32_t phy_addr; uint32_t phy_size; uint32_t bin_head_ofs = 0; uint32_t bin_preload_ofs = 0; /* skip, if vpu firmware had ready been mapped */ if (vpu_drv && vpu_drv->bin_va) return 0; if (!node) { pr_info("%s: unable to get vpu firmware\n", __func__); return -ENODEV; } if (of_property_read_u32(node, "bin-phy-addr", &phy_addr) || of_property_read_u32(node, "bin-size", &phy_size)) { pr_info("%s: unable to get bin info\n", __func__); return -ENODEV; } if (!of_property_read_u32(node, "img-head", &bin_head_ofs) && !of_property_read_u32(node, "pre-bin", &bin_preload_ofs)) { vpu_drv->bin_type = VPU_IMG_PRELOAD; } else { vpu_drv->bin_type = VPU_IMG_LEGACY; } /* map vpu firmware to kernel virtual address */ vpu_drv->bin_va = vpu_vmap(phy_addr, phy_size); vpu_drv->bin_pa = phy_addr; vpu_drv->bin_size = phy_size; pr_info("%s: mapped vpu firmware: pa: 0x%lx, size: 0x%x, kva: 0x%lx\n", __func__, vpu_drv->bin_pa, vpu_drv->bin_size, (unsigned long)vpu_drv->bin_va); vpu_drv->bin_head_ofs = bin_head_ofs; vpu_drv->bin_preload_ofs = bin_preload_ofs; pr_info("%s: header: 0x%x, preload:0x%x\n", __func__, vpu_drv->bin_head_ofs, vpu_drv->bin_preload_ofs); return 0; } static void vpu_shared_release(struct kref *ref) { vpu_drv_debug("%s:\n", __func__); if (vpu_drv->mva_algo) { vpu_drv->vp->mops->free(vpu_drv->iova_dev, &vpu_drv->iova_algo); vpu_drv->mva_algo = 0; } } static int vpu_shared_put(struct platform_device *pdev, struct vpu_device *vd) { vpu_drv->iova_dev = &pdev->dev; if (vpu_drv->mva_algo) kref_put(&vpu_drv->iova_ref, vpu_shared_release); return 0; } static int vpu_shared_get(struct platform_device *pdev, struct vpu_device *vd) { dma_addr_t iova = 0; struct vpu_mem_ops *mops = vpu_drv->vp->mops; struct vpu_misc_ops *cops = vpu_drv->vp->cops; if (vpu_drv->mva_algo) { kref_get(&vpu_drv->iova_ref); return 0; } kref_init(&vpu_drv->iova_ref); if (!vpu_drv->mva_algo) { cops->emi_mpu_set(vpu_drv->bin_pa, vpu_drv->bin_size); if (mops->dts(vd->dev, "algo", &vpu_drv->iova_algo)) goto error; iova = mops->alloc(vd->dev, &vpu_drv->iova_algo); if (!iova) goto error; vpu_drv->mva_algo = iova; vpu_drv->iova_algo.addr = iova; } if (!vpu_drv->mva_cfg) { vpu_drv->iova_cfg.bin = VPU_MEM_ALLOC; vpu_drv->iova_cfg.size = __ALIGN_KERNEL(sizeof(struct vpu_misc), 0x1000); iova = mops->alloc(vd->dev, &vpu_drv->iova_cfg); if (!iova) goto error; vpu_drv->mva_cfg = iova; vpu_drv->iova_cfg.addr = iova; } return 0; error: vpu_shared_put(pdev, vd); return -ENOMEM; } static int vpu_exit_dev_mem(struct platform_device *pdev, struct vpu_device *vd) { struct vpu_mem_ops *mops = vd_mops(vd); vpu_drv_debug("%s: vpu%d\n", __func__, vd->id); mops->free(&pdev->dev, &vd->iova_reset); mops->free(&pdev->dev, &vd->iova_main); mops->free(&pdev->dev, &vd->iova_work); mops->free(&pdev->dev, &vd->iova_iram); vpu_shared_put(pdev, vd); return 0; } static int vpu_iomem_dts(struct platform_device *pdev, const char *name, int i, struct vpu_iomem *m) { if (!m) return 0; m->res = platform_get_resource(pdev, IORESOURCE_MEM, i); if (!m->res) { dev_info(&pdev->dev, "unable to get resource: %s\n", name); return -ENODEV; } m->m = devm_ioremap_resource(&pdev->dev, m->res); if (!m->m) { dev_info(&pdev->dev, "unable to map iomem: %s\n", name); return -ENODEV; } dev_info(&pdev->dev, "mapped %s: 0x%lx: 0x%lx ~ 0x%lx\n", name, (unsigned long)m->m, (unsigned long)m->res->start, (unsigned long)m->res->end); return 0; } static int vpu_init_dev_mem(struct platform_device *pdev, struct vpu_device *vd) { struct resource *res; dma_addr_t iova = 0; int ret = 0; struct vpu_mem_ops *mops = vd_mops(vd); struct vpu_config *cfg = vd_cfg(vd); struct device *dev = vd->dev; /* registers */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (vpu_iomem_dts(pdev, "reg", 0, &vd->reg) || vpu_iomem_dts(pdev, "dmem", 1, &vd->dmem) || vpu_iomem_dts(pdev, "imem", 2, &vd->imem) || vpu_iomem_dts(pdev, "dbg", 3, &vd->dbg)) { goto error; } /* iova */ if (mops->dts(dev, "reset-vector", &vd->iova_reset) || mops->dts(dev, "main-prog", &vd->iova_main) || mops->dts(dev, "kernel-lib", &vd->iova_kernel) || mops->dts(dev, "iram-data", &vd->iova_iram) || mops->dts(dev, "work-buf", &vd->iova_work)) { goto error; } if (vd->iova_work.size < (cfg->log_ofs + cfg->log_header_sz)) goto error; vd->wb_log_size = vd->iova_work.size - cfg->log_ofs; vd->wb_log_data = vd->wb_log_size - cfg->log_header_sz; ret = vpu_shared_get(pdev, vd); if (ret) goto error; iova = mops->alloc(dev, &vd->iova_reset); if (!iova) goto error; iova = mops->alloc(dev, &vd->iova_main); if (!iova) goto error; iova = mops->alloc(dev, &vd->iova_work); if (!iova) goto error; iova = mops->alloc(dev, &vd->iova_iram); vd->mva_iram = iova; vd->iova_iram.addr = iova; return 0; error: return -ENOMEM; } static int vpu_init_dev_irq_rv(struct platform_device *pdev, struct vpu_device *vd) { return 0; } static int vpu_init_dev_irq(struct platform_device *pdev, struct vpu_device *vd) { vd->irq_num = irq_of_parse_and_map(pdev->dev.of_node, 0); if (vd->irq_num <= 0) { pr_info("%s: %s: invalid IRQ: %d\n", __func__, vd->name, vd->irq_num); return -ENODEV; } pr_info("%s: %s: IRQ: %d\n", __func__, vd->name, vd->irq_num); return 0; } static int vpu_init_adev_rv(struct vpu_device *vd, struct apusys_device *adev, int type, cmd_handler_t hndl) { return 0; } static int vpu_init_adev(struct vpu_device *vd, struct apusys_device *adev, int type, cmd_handler_t hndl) { int ret; adev->dev_type = type; adev->preempt_type = APUSYS_PREEMPT_WAITCOMPLETED; adev->private = vd; adev->send_cmd = hndl; memset(adev->meta_data, 0, sizeof(adev->meta_data)); ret = apusys_register_device(adev); if (ret) pr_info("%s: type: %d, ret: %d\n", __func__, type, ret); return ret; } static int vpu_init_drv_plat(struct vpu_platform *vp) { int ret = 0; if (!vp) return -EINVAL; if (vp->mops && vp->mops->init) ret = vp->mops->init(); return ret; } static void vpu_exit_drv_plat(void) { struct vpu_platform *vp; if (!vpu_drv || !vpu_drv->vp) return; vp = vpu_drv->vp; if (vp->mops && vp->mops->exit) vp->mops->exit(); } static int vpu_init_dev_plat(struct platform_device *pdev, struct vpu_device *vd) { int ret = 0; struct vpu_platform *vp; vp = (struct vpu_platform *)of_device_get_match_data(&pdev->dev); if (!vp) { dev_info(&pdev->dev, "unsupported device: %s\n", pdev->name); return -ENODEV; } vd->drv = vpu_drv; /* assign driver platform operations & platform init */ mutex_lock(&vpu_drv->lock); if (!vpu_drv->vp) { vpu_drv->vp = vp; ret = vpu_init_drv_plat(vp); if (ret) vpu_drv->vp = NULL; } else if (vpu_drv->vp != vp) { dev_info(&pdev->dev, "device platform ops mispatch\n"); ret = -EINVAL; } mutex_unlock(&vpu_drv->lock); return ret; } static int vpu_probe(struct platform_device *pdev) { struct vpu_device *vd; struct vpu_probe_ops *probe_ops = vd_probe_ops(vd); int ret = 0; vd = vpu_alloc(pdev); if (!vd) return -ENOMEM; ret = of_property_read_u32(pdev->dev.of_node, "id", &vd->id); if (ret) { dev_info(&pdev->dev, "unable to get core ID: %d\n", ret); goto out; } if (vd->id == 0) { vpu_drv = kzalloc(sizeof(struct vpu_driver), GFP_KERNEL); if (!vpu_drv) return -ENOMEM; kref_init(&vpu_drv->ref); ret = vpu_init_bin(pdev->dev.of_node); if (ret) goto out; vpu_init_debug(); INIT_LIST_HEAD(&vpu_drv->devs); mutex_init(&vpu_drv->lock); vpu_drv->mva_algo = 0; vpu_drv->wq = create_workqueue("vpu_wq"); vpu_init_drv_hw(); vpu_init_drv_met(); vpu_init_drv_tags(); } ret = snprintf(vd->name, sizeof(vd->name), "vpu%d", vd->id); if (ret < 0) goto out; ret = vpu_init_dev_plat(pdev, vd); if (ret) goto out; if (vpu_drv->bin_type != vpu_drv->vp->cfg->bin_type) { dev_info(&pdev->dev, "DTS bin type %d mispatch with device %d\n", vpu_drv->bin_type, vpu_drv->vp->cfg->bin_type); ret = -ENODEV; goto out; } /* put efuse judgement at beginning */ if (vpu_drv->vp->cops->is_disabled(vd)) { ret = -ENODEV; vd->state = VS_DISALBED; goto out; } else { vd->state = VS_DOWN; } /* allocate resources */ ret = vpu_init_dev_mem(pdev, vd); if (ret) goto free; ret = probe_ops->init_dev_irq(pdev, vd); if (ret) goto free; /* device hw initialization */ ret = vpu_init_dev_hw(pdev, vd); if (ret) goto free; /* power initialization */ ret = probe_ops->init_power(pdev, vd); if (ret) goto hw_free; /* device algo initialization */ ret = vpu_init_dev_algo(pdev, vd); if (ret) goto hw_free; /* register device to APUSYS */ ret = probe_ops->init_adev(vd, &vd->adev, APUSYS_DEVICE_VPU, vpu_send_cmd); if (ret) goto hw_free; if (xos_type(vd) == VPU_XOS) { ret = probe_ops->init_adev(vd, &vd->adev_rt, APUSYS_DEVICE_VPU_RT, vpu_send_cmd_rt); if (ret) goto hw_free; } vpu_init_dev_debug(pdev, vd); vpu_init_dev_met(pdev, vd); vpu_dev_add(vd); dev_info(&pdev->dev, "%s: succeed\n", __func__); return 0; // TODO: add error handling free algo hw_free: vpu_exit_dev_hw(pdev, vd); free: vpu_exit_dev_mem(pdev, vd); out: vpu_free(pdev); dev_info(&pdev->dev, "%s: failed\n", __func__); return ret; } static int vpu_remove(struct platform_device *pdev) { struct vpu_device *vd = platform_get_drvdata(pdev); dev_info(&pdev->dev, "%s\n", __func__); vpu_exit_dev_met(pdev, vd); vpu_exit_dev_debug(pdev, vd); vpu_exit_dev_hw(pdev, vd); vpu_exit_dev_algo(pdev, vd); vpu_exit_dev_mem(pdev, vd); disable_irq(vd->irq_num); apusys_unregister_device(&vd->adev); if (xos_type(vd) == VPU_XOS) apusys_unregister_device(&vd->adev_rt); vpu_exit_dev_pwr(pdev, vd); vpu_dev_del(vd); vpu_free(pdev); return 0; } static int vpu_suspend(struct vpu_device *vd) { vpu_cmd_lock_all(vd); vpu_pwr_debug("%s: pw_ref: %d, state: %d\n", __func__, vpu_pwr_cnt(vd), vd->state); if (!vpu_pwr_cnt(vd) && (vd->state != VS_DOWN)) { vpu_pwr_suspend_locked(vd); vpu_pwr_debug("%s: suspended\n", __func__); } vpu_cmd_unlock_all(vd); return 0; } static int vpu_resume(struct vpu_device *vd) { return 0; } struct vpu_probe_ops vpu_probe_ops_ap = { .init_adev = vpu_init_adev, .init_power = vpu_init_dev_pwr, .init_dev_irq = vpu_init_dev_irq, }; struct vpu_probe_ops vpu_probe_ops_rv = { .init_adev = vpu_init_adev_rv, .init_power = vpu_init_dev_pwr_rv, .init_dev_irq = vpu_init_dev_irq_rv, }; struct vpu_platform vpu_plat_mt8188 = { .bops = &vpu_bops_preload, .mops = &vpu_mops_v2, .sops = &vpu_sops_mt68xx, .cops = &vpu_cops_mt8188, .reg = &vpu_reg_mt68xx, .cfg = &vpu_cfg_mt68xx, .cmd_ops = &vpu_cmd_ops_rv, .probe_ops = &vpu_probe_ops_rv, .core_num = 1, }; struct vpu_platform vpu_plat_mt6885 = { .bops = &vpu_bops_preload, .mops = &vpu_mops_v2, .sops = &vpu_sops_mt68xx, .cops = &vpu_cops_mt6885, .reg = &vpu_reg_mt68xx, .cfg = &vpu_cfg_mt68xx, .cmd_ops = &vpu_cmd_ops_ap, .probe_ops = &vpu_probe_ops_ap, .core_num = 2, }; struct vpu_platform vpu_plat_mt68xx = { .bops = &vpu_bops_preload, .mops = &vpu_mops_v2, .sops = &vpu_sops_mt68xx, .cops = &vpu_cops_mt68xx, .reg = &vpu_reg_mt68xx, .cfg = &vpu_cfg_mt68xx, .cmd_ops = &vpu_cmd_ops_ap, .probe_ops = &vpu_probe_ops_ap, .core_num = 2, }; struct vpu_platform vpu_plat_mt67xx = { .bops = &vpu_bops_legacy, .mops = &vpu_mops_v2, .sops = &vpu_sops_mt67xx, .cops = &vpu_cops_mt67xx, .reg = &vpu_reg_mt67xx, .cfg = &vpu_cfg_mt67xx, .cmd_ops = &vpu_cmd_ops_ap, .probe_ops = &vpu_probe_ops_ap, .core_num = 2, }; static const struct of_device_id vpu_of_ids[] = { {.compatible = "mediatek,mt6885-vpu_core", .data = &vpu_plat_mt6885}, {.compatible = "mediatek,mt6873-vpu_core", .data = &vpu_plat_mt68xx}, {.compatible = "mediatek,mt6853-vpu_core", .data = &vpu_plat_mt68xx}, {.compatible = "mediatek,mt6893-vpu_core", .data = &vpu_plat_mt68xx}, {.compatible = "mediatek,mt6785-vpu_core", .data = &vpu_plat_mt67xx}, {.compatible = "mediatek,mt6779-vpu_core", .data = &vpu_plat_mt67xx}, {.compatible = "mediatek,mt8188-vpu_core", .data = &vpu_plat_mt8188}, {} }; MODULE_DEVICE_TABLE(of, vpu_of_ids); static struct platform_driver vpu_plat_drv = { .probe = vpu_probe, .remove = vpu_remove, .driver = { .name = "vpu", .owner = THIS_MODULE, .of_match_table = vpu_of_ids, } }; static void vpu_drv_release(struct kref *ref) { vpu_drv_debug("%s:\n", __func__); vpu_exit_drv_plat(); vpu_drv_debug("%s: destroy workqueue\n", __func__); if (vpu_drv->wq) { flush_workqueue(vpu_drv->wq); destroy_workqueue(vpu_drv->wq); vpu_drv->wq = NULL; } vpu_drv_debug("%s: iounmap\n", __func__); if (vpu_drv->bin_va) { iounmap(vpu_drv->bin_va); vpu_drv->bin_va = NULL; } kfree(vpu_drv); vpu_drv = NULL; } int vpu_init(struct apusys_core_info *info) { int ret; if (!apusys_power_check()) { pr_info("%s: vpu is disabled by apusys\n", __func__); return -ENODEV; } vpu_init_algo(); ret = platform_driver_register(&vpu_plat_drv); return ret; } void vpu_exit(void) { struct vpu_device *vd; struct list_head *ptr, *tmp; if (!vpu_drv) { vpu_drv_debug("%s: platform_driver_unregister\n", __func__); platform_driver_unregister(&vpu_plat_drv); return; } /* notify all devices that we are going to be removed * wait and stop all on-going requests **/ mutex_lock(&vpu_drv->lock); list_for_each_safe(ptr, tmp, &vpu_drv->devs) { vd = list_entry(ptr, struct vpu_device, list); vpu_cmd_lock_all(vd); vd->state = VS_REMOVING; vpu_cmd_unlock_all(vd); } mutex_unlock(&vpu_drv->lock); vpu_drv_debug("%s: platform_driver_unregister\n", __func__); platform_driver_unregister(&vpu_plat_drv); vpu_exit_drv_tags(); vpu_exit_drv_met(); vpu_exit_drv_hw(); vpu_exit_debug(); vpu_drv_put(); }