503 lines
13 KiB
C
503 lines
13 KiB
C
/* 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 <asm/dma-iommu.h>
|
|
#include <asm/page.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/qcom_iommu.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/slab.h>
|
|
#include <soc/qcom/subsystem_notif.h>
|
|
#include <soc/qcom/subsystem_restart.h>
|
|
#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);
|
|
}
|