/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that 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. */ #define VIDC_DBG_LABEL "venus_boot" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "msm_vidc_debug.h" #include "vidc_hfi_io.h" #include "venus_boot.h" /* VENUS WRAPPER registers */ #define VENUS_WRAPPER_VBIF_SS_SEC_CPA_START_ADDR_v1 \ (VIDC_WRAPPER_BASE_OFFS + 0x1018) #define VENUS_WRAPPER_VBIF_SS_SEC_CPA_END_ADDR_v1 \ (VIDC_WRAPPER_BASE_OFFS + 0x101C) #define VENUS_WRAPPER_VBIF_SS_SEC_FW_START_ADDR_v1 \ (VIDC_WRAPPER_BASE_OFFS + 0x1020) #define VENUS_WRAPPER_VBIF_SS_SEC_FW_END_ADDR_v1 \ (VIDC_WRAPPER_BASE_OFFS + 0x1024) #define VENUS_WRAPPER_VBIF_SS_SEC_CPA_START_ADDR_v2 \ (VIDC_WRAPPER_BASE_OFFS + 0x1020) #define VENUS_WRAPPER_VBIF_SS_SEC_CPA_END_ADDR_v2 \ (VIDC_WRAPPER_BASE_OFFS + 0x1024) #define VENUS_WRAPPER_VBIF_SS_SEC_FW_START_ADDR_v2 \ (VIDC_WRAPPER_BASE_OFFS + 0x1028) #define VENUS_WRAPPER_VBIF_SS_SEC_FW_END_ADDR_v2 \ (VIDC_WRAPPER_BASE_OFFS + 0x102C) #define VENUS_WRAPPER_SW_RESET (VIDC_WRAPPER_BASE_OFFS + 0x3000) /* VENUS VBIF registers */ #define VENUS_VBIF_CLKON_FORCE_ON BIT(0) #define VENUS_VBIF_ADDR_TRANS_EN (VIDC_VBIF_BASE_OFFS + 0x1000) #define VENUS_VBIF_AT_OLD_BASE (VIDC_VBIF_BASE_OFFS + 0x1004) #define VENUS_VBIF_AT_OLD_HIGH (VIDC_VBIF_BASE_OFFS + 0x1008) #define VENUS_VBIF_AT_NEW_BASE (VIDC_VBIF_BASE_OFFS + 0x1010) #define VENUS_VBIF_AT_NEW_HIGH (VIDC_VBIF_BASE_OFFS + 0x1018) /* Poll interval in uS */ #define POLL_INTERVAL_US 50 #define VENUS_REGION_SIZE 0x00500000 static struct { struct msm_vidc_platform_resources *resources; struct regulator *gdsc; const char *reg_name; void __iomem *reg_base; struct device *iommu_ctx_bank_dev; struct dma_iommu_mapping *mapping; dma_addr_t fw_iova; bool is_booted; bool hw_ver_checked; u32 fw_sz; u32 hw_ver_major; u32 hw_ver_minor; void *venus_notif_hdle; } *venus_data = NULL; /* Get venus clocks and set rates for rate-settable clocks */ static int venus_clock_setup(void) { int i, rc = 0; unsigned long rate; struct msm_vidc_platform_resources *res = venus_data->resources; struct clock_info *cl; for (i = 0; i < res->clock_set.count; i++) { cl = &res->clock_set.clock_tbl[i]; /* Make sure rate-settable clocks' rates are set */ if (!clk_get_rate(cl->clk) && cl->count) { rate = clk_round_rate(cl->clk, 0); rc = clk_set_rate(cl->clk, rate); if (rc) { dprintk(VIDC_ERR, "Failed to set clock rate %lu %s: %d\n", rate, cl->name, rc); break; } } } return rc; } static int venus_clock_prepare_enable(void) { int i, rc = 0; struct msm_vidc_platform_resources *res = venus_data->resources; struct clock_info *cl; for (i = 0; i < res->clock_set.count; i++) { cl = &res->clock_set.clock_tbl[i]; rc = clk_prepare_enable(cl->clk); if (rc) { dprintk(VIDC_ERR, "failed to enable %s\n", cl->name); for (i--; i >= 0; i--) { cl = &res->clock_set.clock_tbl[i]; clk_disable_unprepare(cl->clk); } return rc; } } return rc; } static void venus_clock_disable_unprepare(void) { int i; struct msm_vidc_platform_resources *res = venus_data->resources; struct clock_info *cl; for (i = 0; i < res->clock_set.count; i++) { cl = &res->clock_set.clock_tbl[i]; clk_disable_unprepare(cl->clk); } } static int venus_setup_cb(struct device *dev, u32 size) { dma_addr_t va_start = 0x0; size_t va_size = size; venus_data->mapping = arm_iommu_create_mapping( msm_iommu_get_bus(dev), va_start, va_size); if (IS_ERR_OR_NULL(venus_data->mapping)) { dprintk(VIDC_ERR, "%s: failed to create mapping for %s\n", __func__, dev_name(dev)); return -ENODEV; } dprintk(VIDC_DBG, "%s Attached device %p and created mapping %p for %s\n", __func__, dev, venus_data->mapping, dev_name(dev)); return 0; } static int pil_venus_mem_setup(size_t size) { int rc = 0; if (!venus_data->mapping) { size = round_up(size, SZ_4K); rc = venus_setup_cb(venus_data->iommu_ctx_bank_dev, size); if (rc) { dprintk(VIDC_ERR, "%s: Failed to setup context bank for venus : %s\n", __func__, dev_name(venus_data->iommu_ctx_bank_dev)); return rc; } venus_data->fw_sz = size; } return rc; } static int pil_venus_auth_and_reset(void) { int rc; /* Need to enable this for new SMMU to set the device attribute */ bool disable_htw = true; phys_addr_t fw_bias = venus_data->resources->firmware_base; void __iomem *reg_base = venus_data->reg_base; u32 ver; bool iommu_present = is_iommu_present(venus_data->resources); struct device *dev = venus_data->iommu_ctx_bank_dev; if (!fw_bias) { dprintk(VIDC_ERR, "FW bias is not valid\n"); return -EINVAL; } venus_data->fw_iova = (dma_addr_t)NULL; /* Get Venus version number */ if (!venus_data->hw_ver_checked) { ver = readl_relaxed(reg_base + VIDC_WRAPPER_HW_VERSION); venus_data->hw_ver_minor = (ver & 0x0FFF0000) >> 16; venus_data->hw_ver_major = (ver & 0xF0000000) >> 28; venus_data->hw_ver_checked = 1; } if (iommu_present) { u32 cpa_start_addr, cpa_end_addr, fw_start_addr, fw_end_addr; /* Get the cpa and fw start/end addr based on Venus version */ if (venus_data->hw_ver_major == 0x1 && venus_data->hw_ver_minor <= 1) { cpa_start_addr = VENUS_WRAPPER_VBIF_SS_SEC_CPA_START_ADDR_v1; cpa_end_addr = VENUS_WRAPPER_VBIF_SS_SEC_CPA_END_ADDR_v1; fw_start_addr = VENUS_WRAPPER_VBIF_SS_SEC_FW_START_ADDR_v1; fw_end_addr = VENUS_WRAPPER_VBIF_SS_SEC_FW_END_ADDR_v1; } else { cpa_start_addr = VENUS_WRAPPER_VBIF_SS_SEC_CPA_START_ADDR_v2; cpa_end_addr = VENUS_WRAPPER_VBIF_SS_SEC_CPA_END_ADDR_v2; fw_start_addr = VENUS_WRAPPER_VBIF_SS_SEC_FW_START_ADDR_v2; fw_end_addr = VENUS_WRAPPER_VBIF_SS_SEC_FW_END_ADDR_v2; } /* Program CPA start and end address */ writel_relaxed(0, reg_base + cpa_start_addr); writel_relaxed(venus_data->fw_sz, reg_base + cpa_end_addr); /* Program FW start and end address */ writel_relaxed(0, reg_base + fw_start_addr); writel_relaxed(venus_data->fw_sz, reg_base + fw_end_addr); } else { rc = regulator_enable(venus_data->gdsc); if (rc) { dprintk(VIDC_ERR, "GDSC enable failed\n"); goto err; } rc = venus_clock_prepare_enable(); if (rc) { dprintk(VIDC_ERR, "Clock prepare and enable failed\n"); regulator_disable(venus_data->gdsc); goto err; } writel_relaxed(0, reg_base + VENUS_VBIF_AT_OLD_BASE); writel_relaxed(VENUS_REGION_SIZE, reg_base + VENUS_VBIF_AT_OLD_HIGH); writel_relaxed(fw_bias, reg_base + VENUS_VBIF_AT_NEW_BASE); writel_relaxed(fw_bias + VENUS_REGION_SIZE, reg_base + VENUS_VBIF_AT_NEW_HIGH); writel_relaxed(0x7F007F, reg_base + VENUS_VBIF_ADDR_TRANS_EN); venus_clock_disable_unprepare(); regulator_disable(venus_data->gdsc); } /* Make sure all register writes are committed. */ mb(); /* * Need to wait 10 cycles of internal clocks before bringing ARM9 * out of reset. */ udelay(1); if (iommu_present) { phys_addr_t pa = fw_bias; /* Enable this for new SMMU to set the device attribute */ rc = iommu_domain_set_attr(venus_data->mapping->domain, DOMAIN_ATTR_COHERENT_HTW_DISABLE, &disable_htw); if (rc) { dprintk(VIDC_ERR, "%s: Failed to disable COHERENT_HTW: %s\n", __func__, dev_name(dev)); goto release_mapping; } rc = arm_iommu_attach_device(dev, venus_data->mapping); if (rc) { dprintk(VIDC_ERR, "Failed to attach iommu for %s : %d\n", dev_name(dev), rc); goto release_mapping; } dprintk(VIDC_DBG, "Attached and created mapping for %s\n", dev_name(dev)); /* Map virtual addr space 0 - fw_sz to fw phys addr space */ rc = iommu_map(venus_data->mapping->domain, venus_data->fw_iova, pa, venus_data->fw_sz, IOMMU_READ|IOMMU_WRITE|IOMMU_PRIV); if (!rc) { dprintk(VIDC_DBG, "%s - Successfully mapped and performed test translation!\n", dev_name(dev)); } if (rc || (venus_data->fw_iova != 0)) { dprintk(VIDC_ERR, "%s - Failed to setup IOMMU\n", dev_name(dev)); goto err_iommu_map; } } /* Bring Arm9 out of reset */ writel_relaxed(0, reg_base + VENUS_WRAPPER_SW_RESET); venus_data->is_booted = 1; return 0; err_iommu_map: if (iommu_present) arm_iommu_detach_device(dev); release_mapping: if (iommu_present) arm_iommu_release_mapping(venus_data->mapping); err: return rc; } static int pil_venus_shutdown(void) { void __iomem *reg_base = venus_data->reg_base; u32 reg; int rc; if (!venus_data->is_booted) return 0; /* Assert the reset to ARM9 */ reg = readl_relaxed(reg_base + VENUS_WRAPPER_SW_RESET); reg |= BIT(4); writel_relaxed(reg, reg_base + VENUS_WRAPPER_SW_RESET); /* Make sure reset is asserted before the mapping is removed */ mb(); if (is_iommu_present(venus_data->resources)) { iommu_unmap(venus_data->mapping->domain, venus_data->fw_iova, venus_data->fw_sz); arm_iommu_detach_device(venus_data->iommu_ctx_bank_dev); } /* * Force the VBIF clk to be on to avoid AXI bridge halt ack failure * for certain Venus version. */ if (venus_data->hw_ver_major == 0x1 && (venus_data->hw_ver_minor == 0x2 || venus_data->hw_ver_minor == 0x3)) { reg = readl_relaxed(reg_base + VIDC_VENUS_VBIF_CLK_ON); reg |= VENUS_VBIF_CLKON_FORCE_ON; writel_relaxed(reg, reg_base + VIDC_VENUS_VBIF_CLK_ON); } /* Halt AXI and AXI OCMEM VBIF Access */ reg = readl_relaxed(reg_base + VENUS_VBIF_AXI_HALT_CTRL0); reg |= VENUS_VBIF_AXI_HALT_CTRL0_HALT_REQ; writel_relaxed(reg, reg_base + VENUS_VBIF_AXI_HALT_CTRL0); /* Request for AXI bus port halt */ rc = readl_poll_timeout(reg_base + VENUS_VBIF_AXI_HALT_CTRL1, reg, reg & VENUS_VBIF_AXI_HALT_CTRL1_HALT_ACK, POLL_INTERVAL_US, VENUS_VBIF_AXI_HALT_ACK_TIMEOUT_US); if (rc) dprintk(VIDC_ERR, "Port halt timeout\n"); venus_data->is_booted = 0; return 0; } static int venus_notifier_cb(struct notifier_block *this, unsigned long code, void *ss_handle) { struct notif_data *data = (struct notif_data *)ss_handle; static bool venus_data_set; int ret; if (!data->no_auth) return NOTIFY_DONE; if (!venus_data_set) { ret = venus_clock_setup(); if (ret) return ret; ret = of_property_read_string(data->pdev->dev.of_node, "qcom,proxy-reg-names", &venus_data->reg_name); if (ret) return ret; venus_data->gdsc = devm_regulator_get( &data->pdev->dev, venus_data->reg_name); if (IS_ERR(venus_data->gdsc)) { dprintk(VIDC_ERR, "Failed to get Venus GDSC\n"); return -ENODEV; } venus_data_set = true; } if (code != SUBSYS_AFTER_POWERUP && code != SUBSYS_AFTER_SHUTDOWN) return NOTIFY_DONE; ret = regulator_enable(venus_data->gdsc); if (ret) { dprintk(VIDC_ERR, "GDSC enable failed\n"); return ret; } ret = venus_clock_prepare_enable(); if (ret) { dprintk(VIDC_ERR, "Clock prepare and enable failed\n"); goto err_clks; } if (code == SUBSYS_AFTER_POWERUP) { if (is_iommu_present(venus_data->resources)) pil_venus_mem_setup(VENUS_REGION_SIZE); pil_venus_auth_and_reset(); } else if (code == SUBSYS_AFTER_SHUTDOWN) pil_venus_shutdown(); venus_clock_disable_unprepare(); regulator_disable(venus_data->gdsc); return NOTIFY_DONE; err_clks: regulator_disable(venus_data->gdsc); return ret; } static struct notifier_block venus_notifier = { .notifier_call = venus_notifier_cb, }; int venus_boot_init(struct msm_vidc_platform_resources *res, struct context_bank_info *cb) { int rc = 0; if (!res || !cb) { dprintk(VIDC_ERR, "Invalid platform resource handle\n"); return -EINVAL; } venus_data = kzalloc(sizeof(*venus_data), GFP_KERNEL); if (!venus_data) return -ENOMEM; venus_data->resources = res; venus_data->iommu_ctx_bank_dev = cb->dev; if (!venus_data->iommu_ctx_bank_dev) { dprintk(VIDC_ERR, "Invalid venus context bank device\n"); return -ENODEV; } venus_data->reg_base = ioremap_nocache(res->register_base, (unsigned long)res->register_size); if (!venus_data->reg_base) { dprintk(VIDC_ERR, "could not map reg addr %pa of size %d\n", &res->register_base, res->register_size); rc = -ENOMEM; goto err_ioremap_fail; } venus_data->venus_notif_hdle = subsys_notif_register_notifier("venus", &venus_notifier); if (IS_ERR(venus_data->venus_notif_hdle)) { dprintk(VIDC_ERR, "register event notification failed\n"); rc = PTR_ERR(venus_data->venus_notif_hdle); goto err_subsys_notif; } return rc; err_subsys_notif: err_ioremap_fail: kfree(venus_data); return rc; } void venus_boot_deinit(void) { venus_data->resources = NULL; subsys_notif_unregister_notifier(venus_data->venus_notif_hdle, &venus_notifier); kfree(venus_data); }