Jetpack/kernel/nvidia/drivers/platform/tegra/tegra_vpr.c

197 lines
4.7 KiB
C

/*
* Copyright (c) 2016-2019 NVIDIA Corporation. 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/ote_protocol.h>
#include <linux/delay.h>
extern phys_addr_t tegra_vpr_start;
extern phys_addr_t tegra_vpr_size;
extern bool tegra_vpr_resize;
static DEFINE_MUTEX(vpr_lock);
static int tegra_vpr_arg(char *options)
{
char *p = options;
tegra_vpr_size = memparse(p, &p);
if (*p == '@')
tegra_vpr_start = memparse(p+1, &p);
pr_info("Found vpr, start=0x%llx size=%llx",
(u64)tegra_vpr_start, (u64)tegra_vpr_size);
return 0;
}
early_param("vpr", tegra_vpr_arg);
static int tegra_vpr_resize_arg(char *options)
{
tegra_vpr_resize = true;
return 0;
}
early_param("vpr_resize", tegra_vpr_resize_arg);
#define NUM_MODULES_IDLE_VPR_RESIZE 3
static struct vpr_user_module_info {
int (*do_idle)(void *);
int (*do_unidle)(void *);
void *data;
} vpr_user_module[NUM_MODULES_IDLE_VPR_RESIZE];
static int _tegra_set_vpr_params(void *vpr_base, size_t vpr_size);
static int tegra_update_resize_cfg(phys_addr_t base , size_t size)
{
int i = 0, err = 0;
#define MAX_RETRIES 6
int retries = MAX_RETRIES;
mutex_lock(&vpr_lock);
retry:
for (; i < NUM_MODULES_IDLE_VPR_RESIZE; i++) {
if (vpr_user_module[i].do_idle) {
err = vpr_user_module[i].do_idle(
vpr_user_module[i].data);
if (err) {
pr_err("%s:%d: %pF failed err:%d\n",
__func__, __LINE__,
vpr_user_module[i].do_idle, err);
break;
}
}
}
if (!err) {
/* Config VPR_BOM/_SIZE in MC */
err = _tegra_set_vpr_params((void *)(uintptr_t)base, size);
if (err)
pr_err("vpr resize to (%p, %zu) failed. err=%d\n",
(void *)(uintptr_t)base, size, err);
else
retries = 0; /* finish */
}
if (retries--) {
pr_err("%s:%d: fail retry=%d",
__func__, __LINE__, MAX_RETRIES - retries);
msleep(1);
goto retry;
}
while (--i >= 0) {
if (!vpr_user_module[i].do_unidle)
continue;
err = vpr_user_module[i].do_unidle(
vpr_user_module[i].data);
if (!err)
continue;
pr_err("%s:%d: %pF failed err:%d. Could be fatal!!\n",
__func__, __LINE__,
vpr_user_module[i].do_unidle, err);
/* vpr resize is success, so return 0 on unidle failure */
err = 0;
}
mutex_unlock(&vpr_lock);
return err;
}
struct dma_resize_notifier_ops vpr_dev_ops = {
.resize = tegra_update_resize_cfg
};
EXPORT_SYMBOL(vpr_dev_ops);
bool tegra_is_vpr_resize_supported(void)
{
return tegra_vpr_resize;
}
EXPORT_SYMBOL(tegra_is_vpr_resize_supported);
/* SMC Definitions*/
#define TE_SMC_PROGRAM_VPR 0x82000003
uint32_t invoke_smc(uint32_t arg0, uintptr_t arg1, uintptr_t arg2);
static int _tegra_set_vpr_params(void *vpr_base, size_t vpr_size)
{
int retval = -EINVAL;
retval = invoke_smc(TE_SMC_PROGRAM_VPR,
(uintptr_t)vpr_base, vpr_size);
if (retval != 0) {
pr_err("%s: smc failed, base 0x%p size %zx, err (0x%x)\n",
__func__, vpr_base, vpr_size, retval);
return -EINVAL;
}
return 0;
}
int tegra_set_vpr_params(void *vpr_base, size_t vpr_size)
{
int ret;
mutex_lock(&vpr_lock);
ret = _tegra_set_vpr_params(vpr_base, vpr_size);
mutex_unlock(&vpr_lock);
return ret;
}
EXPORT_SYMBOL(tegra_set_vpr_params);
void tegra_register_idle_unidle(int (*do_idle)(void *),
int (*do_unidle)(void *),
void *data)
{
int i;
mutex_lock(&vpr_lock);
for (i = 0; i < NUM_MODULES_IDLE_VPR_RESIZE; i++) {
if (do_idle == vpr_user_module[i].do_idle) {
vpr_user_module[i].data = data;
goto unlock;
}
}
for (i = 0; i < NUM_MODULES_IDLE_VPR_RESIZE; i++) {
if (!vpr_user_module[i].do_idle) {
vpr_user_module[i].do_idle = do_idle;
vpr_user_module[i].do_unidle = do_unidle;
vpr_user_module[i].data = data;
break;
}
}
unlock:
mutex_unlock(&vpr_lock);
if (i != NUM_MODULES_IDLE_VPR_RESIZE)
return;
pr_err("%pF,%pF failed to register to be called before vpr resize!!\n",
do_idle, do_unidle);
}
EXPORT_SYMBOL(tegra_register_idle_unidle);
void tegra_unregister_idle_unidle(int (*do_idle)(void *))
{
int i;
mutex_lock(&vpr_lock);
for (i = 0; i < NUM_MODULES_IDLE_VPR_RESIZE; i++) {
if (vpr_user_module[i].do_idle == do_idle) {
vpr_user_module[i].do_idle = NULL;
vpr_user_module[i].do_unidle = NULL;
vpr_user_module[i].data = NULL;
break;
}
}
mutex_unlock(&vpr_lock);
}
EXPORT_SYMBOL(tegra_unregister_idle_unidle);