684 lines
14 KiB
C

/*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/clk.h>
#include <linux/clk/tegra.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/platform_data/tegra_mc.h>
#include <linux/tegra-powergate.h>
#include <linux/notifier.h>
#include "iomap.h"
#define TEGRA124_POWERGATE_NUM (TEGRA_POWERGATE_VIC + 1)
#define MAX_HOTRESET_CLIENT_NUM 4
enum mc_client {
MC_CLIENT_AFI = 0,
MC_CLIENT_DC = 2,
MC_CLIENT_DCB = 3,
MC_CLIENT_ISP = 8,
MC_CLIENT_MSENC = 11,
MC_CLIENT_SATA = 15,
MC_CLIENT_VDE = 16,
MC_CLIENT_VI = 17,
MC_CLIENT_VIC = 18,
MC_CLIENT_XUSB_HOST = 19,
MC_CLIENT_XUSB_DEV = 20,
MC_CLIENT_ISPB = 33,
MC_CLIENT_GPU = 34,
MC_CLIENT_LAST = -1,
};
struct mc_client_info {
enum mc_client hot_reset_clients[MAX_HOTRESET_CLIENT_NUM];
};
static int tegra124_powergate_partition(int id);
static int tegra124_unpowergate_partition(int id);
static struct mc_client_info tegra124_pg_mc_info[TEGRA124_POWERGATE_NUM] = {
[TEGRA_POWERGATE_VDEC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_VDE,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_MPE] = {
.hot_reset_clients = {
[0] = MC_CLIENT_MSENC,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_VENC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_ISP,
[1] = MC_CLIENT_ISPB,
[2] = MC_CLIENT_VI,
[3] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_DIS] = {
.hot_reset_clients = {
[0] = MC_CLIENT_DC,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_DISB] = {
.hot_reset_clients = {
[0] = MC_CLIENT_DCB,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_XUSBA] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_XUSBB] = {
.hot_reset_clients = {
[0] = MC_CLIENT_XUSB_DEV,
[1] = MC_CLIENT_LAST
},
},
[TEGRA_POWERGATE_XUSBC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_XUSB_HOST,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_SOR] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_VIC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_VIC,
[1] = MC_CLIENT_LAST,
},
},
};
static struct powergate_partition_info
tegra124_powergate_partition_info[TEGRA124_POWERGATE_NUM] = {
[TEGRA_POWERGATE_VDEC] = {
.name = "vde",
.clk_info = {
[0] = { .clk_name = "vde" },
},
},
[TEGRA_POWERGATE_MPE] = {
.name = "mpe",
.clk_info = {
[0] = { .clk_name = "msenc" },
},
},
[TEGRA_POWERGATE_VENC] = {
.name = "ve",
.clk_info = {
[0] = { .clk_name = "isp" },
[1] = { .clk_name = "ispb" },
[2] = { .clk_name = "vi" },
[3] = { .clk_name = "csi" },
},
},
[TEGRA_POWERGATE_SOR] = {
.name = "sor",
.clk_info = {
[0] = { .clk_name = "sor0" },
[1] = { .clk_name = "dsia" },
[2] = { .clk_name = "dsib" },
[3] = { .clk_name = "hdmi" },
[4] = { .clk_name = "mipi-cal" },
[5] = { .clk_name = "dpaux" },
},
},
[TEGRA_POWERGATE_DIS] = {
.name = "disa",
.clk_info = {
[0] = { .clk_name = "disp1" },
},
},
[TEGRA_POWERGATE_DISB] = {
.name = "disb",
.clk_info = {
[0] = { .clk_name = "disp2" },
},
},
[TEGRA_POWERGATE_XUSBA] = {
.name = "xusba",
.clk_info = {
[0] = { .clk_name = "xusb_ss" },
},
},
[TEGRA_POWERGATE_XUSBB] = {
.name = "xusbb",
.clk_info = {
[0] = { .clk_name = "xusb_dev" },
},
},
[TEGRA_POWERGATE_XUSBC] = {
.name = "xusbc",
.clk_info = {
[0] = { .clk_name = "xusb_host" },
},
},
[TEGRA_POWERGATE_VIC] = {
.name = "vic",
.clk_info = {
[0] = { .clk_name = "vic03" },
},
},
};
static const char *tegra124_get_powerdomain_name(int id)
{
return tegra124_powergate_partition_info[id].name;
}
static atomic_t ref_count_dispa = ATOMIC_INIT(0);
static atomic_t ref_count_dispb = ATOMIC_INIT(0);
static atomic_t ref_count_sor = ATOMIC_INIT(0);
static atomic_t ref_count_venc = ATOMIC_INIT(0);
static DEFINE_MUTEX(tegra124_powergate_lock);
static bool is_clk_inited;
static bool is_mc_ready;
static struct notifier_block nb;
static void release_clk(struct powergate_partition_info *pg_info, int last)
{
struct partition_clk_info *clk_info;
while (last--) {
clk_info = &pg_info->clk_info[last];
clk_put(clk_info->clk_ptr);
}
}
static void tegra124_powergate_init_clk(void)
{
int i, j;
struct clk *clk;
struct powergate_partition_info *pg_info;
struct partition_clk_info *clk_info;
for (i = 0; i < TEGRA124_POWERGATE_NUM; i++) {
pg_info = &tegra124_powergate_partition_info[i];
if (!pg_info->clk_info)
break;
for (j = 0; j < MAX_CLK_EN_NUM; j++) {
clk_info = &pg_info->clk_info[j];
if (!clk_info || !clk_info->clk_name)
break;
clk = clk_get(NULL, clk_info->clk_name);
if (IS_ERR(clk)) {
pr_err("Tegra124 powergate can't find the clk %s\n",
clk_info->clk_name);
release_clk(pg_info, j);
break;
}
clk_info->clk_ptr = clk;
}
}
is_clk_inited = true;
}
static int enable_clk(int id)
{
int ret, i;
struct clk *clk;
struct powergate_partition_info *pg_info;
struct partition_clk_info *clk_info;
pg_info = &tegra124_powergate_partition_info[id];
for (i = 0; i < MAX_CLK_EN_NUM; i++) {
clk_info = &pg_info->clk_info[i];
clk = clk_info->clk_ptr;
if (!clk)
break;
ret = clk_prepare_enable(clk);
if (ret)
goto err_clk_en;
}
return 0;
err_clk_en:
WARN(1, "Could not enable clk %s, error %d",
pg_info->clk_info[i].clk_name, ret);
while (i--) {
clk_info = &pg_info->clk_info[i];
clk_disable_unprepare(clk_info->clk_ptr);
}
return ret;
}
static void reset_clk(int id, bool assert)
{
int i;
struct clk *clk;
struct powergate_partition_info *pg_info;
struct partition_clk_info *clk_info;
pg_info = &tegra124_powergate_partition_info[id];
for (i = 0; i < MAX_CLK_EN_NUM; i++) {
clk_info = &pg_info->clk_info[i];
clk = clk_info->clk_ptr;
if (!clk)
break;
if (assert)
tegra_periph_reset_assert(clk);
else
tegra_periph_reset_deassert(clk);
}
}
static void disable_clk(int id)
{
int i;
struct clk *clk;
struct powergate_partition_info *pg_info;
struct partition_clk_info *clk_info;
pg_info = &tegra124_powergate_partition_info[id];
for (i = 0; i < MAX_CLK_EN_NUM; i++) {
clk_info = &pg_info->clk_info[i];
clk = clk_info->clk_ptr;
if (!clk)
break;
clk_disable_unprepare(clk);
}
}
static int reset_module(int id)
{
int ret;
reset_clk(id, true);
udelay(10);
ret = enable_clk(id);
if (ret)
return ret;
udelay(10);
reset_clk(id, false);
disable_clk(id);
return 0;
}
/*
* MC related internal functions
*/
static int mc_flush(int id)
{
u32 i;
enum mc_client mc_client_bit;
if (!is_mc_ready) {
WARN(1, "Tegra124 memory controller is not ready\n");
return -EPERM;
}
for (i = 0; i < MAX_HOTRESET_CLIENT_NUM; i++) {
mc_client_bit =
tegra124_pg_mc_info[id].hot_reset_clients[i];
if (mc_client_bit == MC_CLIENT_LAST)
break;
tegra_mc_flush(mc_client_bit);
}
return 0;
}
static int mc_flush_done(int id)
{
u32 i;
enum mc_client mc_client_bit;
if (!is_mc_ready) {
WARN(1, "Tegra124 memory controller is not ready\n");
return -EPERM;
}
for (i = 0; i < MAX_HOTRESET_CLIENT_NUM; i++) {
mc_client_bit =
tegra124_pg_mc_info[id].hot_reset_clients[i];
if (mc_client_bit == MC_CLIENT_LAST)
break;
tegra_mc_flush_done(mc_client_bit);
}
return 0;
}
/**
* do_powergate - power gating routine
*
* @id: power partition
*/
static int do_powergate(int id)
{
int ret;
ret = enable_clk(id);
if (ret)
WARN(1, "Couldn't enable clock");
udelay(10);
mc_flush(id);
udelay(10);
reset_clk(id, true);
udelay(10);
/* Powergating is done only if refcnt of all clks is 0 */
disable_clk(id);
udelay(10);
ret = tegra_powergate_power_off(id);
if (ret)
goto err_power_off;
return 0;
err_power_off:
WARN(1, "Could not Powergate Partition %d", id);
return ret;
}
/**
* do_unpowergate - power ungating routine
*
* @id: power partition
* @reset_needed: reset or not when the power is already on
*/
static int do_unpowergate(int id, int reset_needed)
{
int ret;
if (tegra_powergate_is_powered(id))
return reset_needed ? reset_module(id) : 0;
ret = tegra_powergate_power_on(id);
if (ret)
goto err_power;
udelay(10);
/* Un-Powergating fails if all clks are not enabled */
ret = enable_clk(id);
if (ret)
goto err_clk_on;
udelay(10);
ret = tegra_powergate_remove_clamping(id);
if (ret)
goto err_clamp;
udelay(10);
/* deassert reset */
reset_clk(id, false);
udelay(10);
mc_flush_done(id);
udelay(10);
/* Disable all clks enabled earlier. Drivers should enable clks */
disable_clk(id);
return 0;
err_clamp:
disable_clk(id);
err_clk_on:
tegra_powergate_power_off(id);
err_power:
WARN(1, "Could not Un-Powergate %d", id);
return ret;
}
/**
* do_group_powergate - solve the dependency between power domains
*
* @id: the power domain to be shut off
*
* "->" means "depends on"
* VENC -> DISA
* DISB -> DISA & SOR
* DISA -> SOR
*/
static int do_group_powergate(int id)
{
int ret;
int counta = atomic_read(&ref_count_dispa);
int countb = atomic_read(&ref_count_dispb);
int countsor = atomic_read(&ref_count_sor);
int countvenc = atomic_read(&ref_count_venc);
switch (id) {
case TEGRA_POWERGATE_DIS:
if (WARN_ON(counta <= countvenc || counta <= countb))
return -EINVAL;
counta = atomic_dec_return(&ref_count_dispa);
countsor = atomic_dec_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_DISB:
countb = atomic_dec_return(&ref_count_dispb);
counta = atomic_dec_return(&ref_count_dispa);
countsor = atomic_dec_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_VENC:
countvenc = atomic_dec_return(&ref_count_venc);
counta = atomic_dec_return(&ref_count_dispa);
countsor = atomic_dec_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_SOR:
if (WARN_ON(countsor <= counta || countsor <= countb))
return -EINVAL;
countsor = atomic_dec_return(&ref_count_sor);
break;
default:
break;
}
WARN_ONCE(counta < 0, "DISPA ref count underflow");
WARN_ONCE(countb < 0, "DISPB ref count underflow");
WARN_ONCE(countsor < 0, "SOR ref count underflow");
WARN_ONCE(countvenc < 0, "VENC ref count underflow");
ret = 0;
if (countvenc <= 0)
ret = do_powergate(TEGRA_POWERGATE_VENC);
if (countb <= 0 && !ret)
ret = do_powergate(TEGRA_POWERGATE_DISB);
if (counta <= 0 && !ret)
ret = do_powergate(TEGRA_POWERGATE_DIS);
if (countsor <= 0 && !ret)
ret = do_powergate(TEGRA_POWERGATE_SOR);
return ret;
}
/**
* do_group_unpowergate - solve the dependency between power domains
*
* @id: the power domain to be powered on
*
* "->" means "depends on"
* VENC -> DISA
* DISB -> DISA & SOR
* DISA -> SOR
*/
static int do_group_unpowergate(int id)
{
int ret;
int counta = atomic_read(&ref_count_dispa);
int countb = atomic_read(&ref_count_dispb);
int countsor = atomic_read(&ref_count_sor);
int countvenc = atomic_read(&ref_count_venc);
switch (id) {
case TEGRA_POWERGATE_DIS:
counta = atomic_inc_return(&ref_count_dispa);
countsor = atomic_inc_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_DISB:
countb = atomic_inc_return(&ref_count_dispb);
counta = atomic_inc_return(&ref_count_dispa);
countsor = atomic_inc_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_VENC:
countvenc = atomic_inc_return(&ref_count_venc);
counta = atomic_inc_return(&ref_count_dispa);
countsor = atomic_inc_return(&ref_count_sor);
break;
case TEGRA_POWERGATE_SOR:
countsor = atomic_inc_return(&ref_count_sor);
break;
default:
break;
}
ret = 0;
if (countsor > 0)
ret = do_unpowergate(TEGRA_POWERGATE_SOR, 0);
if (counta > 0 && !ret)
ret = do_unpowergate(TEGRA_POWERGATE_DIS, 0);
if (countb > 0 && !ret)
ret = do_unpowergate(TEGRA_POWERGATE_DISB, 0);
if (countvenc > 0 && !ret)
ret = do_unpowergate(TEGRA_POWERGATE_VENC, 0);
return ret;
}
static int tegra124_powergate_partition(int id)
{
int ret;
mutex_lock(&tegra124_powergate_lock);
if (!is_clk_inited)
tegra124_powergate_init_clk();
switch (id) {
case TEGRA_POWERGATE_DIS:
case TEGRA_POWERGATE_DISB:
case TEGRA_POWERGATE_VENC:
/*
* SOR should not be powergated by someone outside the powergate code,
* but who knows?
*/
case TEGRA_POWERGATE_SOR:
ret = do_group_powergate(id);
break;
default:
ret = do_powergate(id);
break;
};
mutex_unlock(&tegra124_powergate_lock);
return ret;
}
static int tegra124_unpowergate_partition(int id)
{
int ret;
mutex_lock(&tegra124_powergate_lock);
if (!is_clk_inited)
tegra124_powergate_init_clk();
switch (id) {
case TEGRA_POWERGATE_DIS:
case TEGRA_POWERGATE_DISB:
case TEGRA_POWERGATE_VENC:
/*
* SOR should not be powergated by someone outside the powergate code,
* but who knows?
*/
case TEGRA_POWERGATE_SOR:
ret = do_group_unpowergate(id);
break;
default:
ret = do_unpowergate(id, 1);
break;
};
mutex_unlock(&tegra124_powergate_lock);
return ret;
}
static struct powergate_ops tegra124_powergate_ops = {
.get_powergate_domain_name = tegra124_get_powerdomain_name,
.powergate_partition = tegra124_powergate_partition,
.unpowergate_partition = tegra124_unpowergate_partition,
};
static struct powergate t124_powergate = {
.soc_name = "tegra124",
.num_powerdomains = TEGRA124_POWERGATE_NUM,
.num_cpu_domains = 0,
.cpu_domain_map = NULL,
.ops = &tegra124_powergate_ops,
};
static int tegra124_powergate_clean(struct notifier_block *self,
unsigned long event, void *data)
{
is_mc_ready = true;
if (!is_clk_inited)
tegra124_powergate_init_clk();
/* Powergate venc/dis/disb/sor to get a clean hardware environment. */
WARN(do_powergate(TEGRA_POWERGATE_VENC), "Powergate VENC failed.");
WARN(do_powergate(TEGRA_POWERGATE_DISB), "Powergate DISB failed.");
WARN(do_powergate(TEGRA_POWERGATE_DIS), "Powergate DIS failed.");
WARN(do_powergate(TEGRA_POWERGATE_SOR), "Powergate SOR failed.");
return NOTIFY_DONE;
}
struct powergate * __init tegra124_powergate_init(void)
{
memset(&nb, 0, sizeof(nb));
nb.notifier_call = tegra124_powergate_clean;
tegra124_mc_register_notify(&nb);
return &t124_powergate;
}