tegrakernel/kernel/nvidia/drivers/platform/tegra/powergate/powergate-t186.c

249 lines
6.6 KiB
C
Raw Permalink Normal View History

2022-02-16 09:13:02 -06:00
/*
* Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <soc/tegra/tegra_powergate.h>
#include <soc/tegra/bpmp_abi.h>
#include <soc/tegra/tegra_bpmp.h>
#include <soc/tegra/tegra-powergate-driver.h>
struct pg_partition_info {
const char *name;
int refcount;
int run_refcount;
struct mutex pg_mutex;
};
static struct pg_partition_info t186_partition_info[] = {
[TEGRA186_POWER_DOMAIN_AUD] = { .name = "audio" },
[TEGRA186_POWER_DOMAIN_DFD] = { .name = "dfd" },
[TEGRA186_POWER_DOMAIN_DISP] = { .name = "disp" },
[TEGRA186_POWER_DOMAIN_DISPB] = { .name = "dispb" },
[TEGRA186_POWER_DOMAIN_DISPC] = { .name = "dispc" },
[TEGRA186_POWER_DOMAIN_ISPA] = { .name = "ispa" },
[TEGRA186_POWER_DOMAIN_NVDEC] = { .name = "nvdec" },
[TEGRA186_POWER_DOMAIN_NVJPG] = { .name = "nvjpg" },
[TEGRA186_POWER_DOMAIN_MPE] = { .name = "nvenc" },
[TEGRA186_POWER_DOMAIN_PCX] = { .name = "pcie" },
[TEGRA186_POWER_DOMAIN_SAX] = { .name = "sata" },
[TEGRA186_POWER_DOMAIN_VE] = { .name = "ve" },
[TEGRA186_POWER_DOMAIN_VIC] = { .name = "vic" },
[TEGRA186_POWER_DOMAIN_XUSBA] = { .name = "xusba" },
[TEGRA186_POWER_DOMAIN_XUSBB] = { .name = "xusbb" },
[TEGRA186_POWER_DOMAIN_XUSBC] = { .name = "xusbc" },
[TEGRA186_POWER_DOMAIN_GPU] = { .name = "gpu" },
};
static int pg_set_state(int id, u32 state)
{
struct mrq_pg_request req = {
.cmd = CMD_PG_SET_STATE,
.id = id,
.set_state = {
.state = state,
}
};
return tegra_bpmp_send_receive(MRQ_PG, &req, sizeof(req), NULL, 0);
}
static int tegra186_pg_query_abi(void)
{
int ret;
struct mrq_query_abi_request req = { .mrq = MRQ_PG };
struct mrq_query_abi_response resp;
ret = tegra_bpmp_send_receive(MRQ_QUERY_ABI, &req, sizeof(req), &resp,
sizeof(resp));
if (ret)
return ret;
return resp.status;
}
static int tegra186_pg_powergate_partition(int id)
{
int ret = 0;
struct pg_partition_info *partition =
&t186_partition_info[id];
mutex_lock(&partition->pg_mutex);
if (partition->refcount) {
if (--partition->refcount == 0)
ret = pg_set_state(id, PG_STATE_OFF);
} else {
WARN(1, "partition %s refcount underflow\n",
partition->name);
}
mutex_unlock(&partition->pg_mutex);
return ret;
}
static int tegra186_pg_unpowergate_partition(int id)
{
int ret = 0;
struct pg_partition_info *partition =
&t186_partition_info[id];
mutex_lock(&partition->pg_mutex);
if (partition->refcount++ == 0)
ret = pg_set_state(id, PG_STATE_ON);
mutex_unlock(&partition->pg_mutex);
return ret;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
static int tegra186_pg_powergate_clk_off(int id)
{
int ret = 0;
struct pg_partition_info *partition =
&t186_partition_info[id];
mutex_lock(&partition->pg_mutex);
if (partition->refcount) {
if (--partition->refcount == 0)
ret = pg_set_state(id, PG_STATE_OFF);
else if (partition->run_refcount == 1)
ret = pg_set_state(id, PG_STATE_ON);
if (partition->run_refcount)
partition->run_refcount--;
} else {
WARN(1, "partition %s refcount underflow\n",
partition->name);
}
mutex_unlock(&partition->pg_mutex);
return ret;
}
static int tegra186_pg_unpowergate_clk_on(int id)
{
int ret = 0;
struct pg_partition_info *partition =
&t186_partition_info[id];
mutex_lock(&partition->pg_mutex);
if (partition->refcount++ == 0 || partition->run_refcount == 0)
ret = pg_set_state(id, PG_STATE_RUNNING);
partition->run_refcount++;
mutex_unlock(&partition->pg_mutex);
return ret;
}
#endif
static const char *tegra186_pg_get_name(int id)
{
return t186_partition_info[id].name;
}
static bool tegra186_pg_is_powered(int id)
{
int ret;
struct mrq_pg_request req = {
.cmd = CMD_PG_GET_STATE,
.id = id,
};
struct mrq_pg_response resp;
ret = tegra_bpmp_send_receive(MRQ_PG, &req, sizeof(req), &resp, sizeof(resp));
if (ret)
return false;
if (resp.get_state.state == PG_STATE_OFF)
return false;
else
return true;
}
static int tegra186_pg_force_powergate(int id)
{
int ret;
ret = pg_set_state(id, PG_STATE_ON);
if (ret)
return ret;
return pg_set_state(id, PG_STATE_OFF);
}
static int tegra186_init_refcount(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(t186_partition_info); ++i)
mutex_init(&t186_partition_info[i].pg_mutex);
tegra186_pg_force_powergate(TEGRA186_POWER_DOMAIN_XUSBA);
tegra186_pg_force_powergate(TEGRA186_POWER_DOMAIN_XUSBB);
tegra186_pg_force_powergate(TEGRA186_POWER_DOMAIN_XUSBC);
tegra186_pg_force_powergate(TEGRA186_POWER_DOMAIN_SAX);
tegra186_pg_force_powergate(TEGRA186_POWER_DOMAIN_PCX);
return 0;
}
static bool tegra186_powergate_id_is_valid(int id)
{
if ((id < 0) || (id >= TEGRA186_POWER_DOMAIN_MAX))
return false;
return true;
}
static struct tegra_powergate_driver_ops tegra186_pg_ops = {
.soc_name = "tegra186",
.num_powerdomains = TEGRA186_POWER_DOMAIN_MAX,
.powergate_id_is_soc_valid = tegra186_powergate_id_is_valid,
.get_powergate_domain_name = tegra186_pg_get_name,
.powergate_partition = tegra186_pg_powergate_partition,
.unpowergate_partition = tegra186_pg_unpowergate_partition,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
.powergate_partition_with_clk_off = tegra186_pg_powergate_clk_off,
.unpowergate_partition_with_clk_on = tegra186_pg_unpowergate_clk_on,
#endif
.powergate_is_powered = tegra186_pg_is_powered,
.powergate_init_refcount = tegra186_init_refcount,
};
struct tegra_powergate_driver_ops *tegra186_powergate_init_chip_support(void)
{
if (tegra186_pg_query_abi()) {
WARN(1, "Missing BPMP support for MRQ_PG\n");
return NULL;
}
return &tegra186_pg_ops;
}