blob: 954d3a39610609d3ac2806fdcb01a47968967cd8 [file] [log] [blame]
/*
* Copyright (c) 2012-2014, 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/spinlock.h>
#include <linux/delay.h>
#include <linux/tegra-powergate.h>
#include <asm/atomic.h>
#include "powergate-priv.h"
#include "powergate-ops-txx.h"
#include "powergate-ops-t1xx.h"
enum mc_client {
MC_CLIENT_AVPC = 1,
MC_CLIENT_DC = 2,
MC_CLIENT_DCB = 3,
MC_CLIENT_EPP = 4,
MC_CLIENT_G2 = 5,
MC_CLIENT_HC = 6,
MC_CLIENT_HDA = 7,
MC_CLIENT_ISP = 8,
MC_CLIENT_MPCORE = 9,
MC_CLIENT_MPCORELP = 10,
MC_CLIENT_MSENC = 11,
MC_CLIENT_NV = 12,
MC_CLIENT_PPCS = 14,
MC_CLIENT_VDE = 16,
MC_CLIENT_VI = 17,
MC_CLIENT_XUSB_HOST = 19,
MC_CLIENT_XUSB_DEV = 20,
MC_CLIENT_EMUCIF = 21,
MC_CLIENT_TSEC = 22,
MC_CLIENT_LAST = -1,
MC_CLIENT_AFI = MC_CLIENT_LAST,
MC_CLIENT_MPE = MC_CLIENT_LAST,
MC_CLIENT_NV2 = MC_CLIENT_LAST,
MC_CLIENT_SATA = MC_CLIENT_LAST,
};
struct tegra11x_powergate_mc_client_info {
enum mc_client hot_reset_clients[MAX_HOTRESET_CLIENT_NUM];
};
static struct tegra11x_powergate_mc_client_info tegra11x_pg_mc_info[] = {
[TEGRA_POWERGATE_CRAIL] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_3D] = {
.hot_reset_clients = {
[0] = MC_CLIENT_NV,
[1] = MC_CLIENT_LAST,
},
},
[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_VI,
[2] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_HEG] = {
.hot_reset_clients = {
[0] = MC_CLIENT_G2,
[1] = MC_CLIENT_EPP,
[2] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_CPU1] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_CPU2] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_CPU3] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_CELP] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_CPU0] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_C0NC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_C1NC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA_POWERGATE_DISA] = {
.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,
},
},
};
static struct powergate_partition_info tegra11x_powergate_partition_info[] = {
[TEGRA_POWERGATE_CRAIL] = { .name = "crail" },
[TEGRA_POWERGATE_3D] = {
.name = "3d",
.clk_info = {
[0] = { .clk_name = "3d", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_VDEC] = {
.name = "vde",
.clk_info = {
[0] = { .clk_name = "vde", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_MPE] = {
.name = "mpe",
.clk_info = {
[0] = { .clk_name = "msenc.cbus", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_VENC] = {
.name = "ve",
.clk_info = {
[0] = { .clk_name = "isp", .clk_type = CLK_AND_RST },
[1] = { .clk_name = "vi", .clk_type = CLK_AND_RST },
[2] = { .clk_name = "csi", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_HEG] = {
.name = "heg",
.clk_info = {
[0] = { .clk_name = "2d.cbus", .clk_type = CLK_AND_RST },
[1] = { .clk_name = "epp.cbus", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_CPU1] = { .name = "cpu1" },
[TEGRA_POWERGATE_CPU2] = { .name = "cpu2" },
[TEGRA_POWERGATE_CPU3] = { .name = "cpu3" },
[TEGRA_POWERGATE_CELP] = { .name = "celp" },
[TEGRA_POWERGATE_CPU0] = { .name = "cpu0" },
[TEGRA_POWERGATE_C0NC] = { .name = "c0nc" },
[TEGRA_POWERGATE_C1NC] = { .name = "c1nc" },
[TEGRA_POWERGATE_DISA] = {
.name = "disa",
.clk_info = {
[0] = { .clk_name = "disp1", .clk_type = CLK_AND_RST },
[1] = { .clk_name = "dsia", .clk_type = CLK_AND_RST },
[2] = { .clk_name = "dsib", .clk_type = CLK_AND_RST },
[3] = { .clk_name = "csi", .clk_type = CLK_AND_RST },
[4] = { .clk_name = "mipi-cal", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_DISB] = {
.name = "disb",
.clk_info = {
[0] = { .clk_name = "disp2", .clk_type = CLK_AND_RST },
[1] = { .clk_name = "hdmi", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_XUSBA] = {
.name = "xusba",
.clk_info = {
[0] = { .clk_name = "xusb_ss", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_XUSBB] = {
.name = "xusbb",
.clk_info = {
[0] = { .clk_name = "xusb_dev", .clk_type = CLK_AND_RST },
},
},
[TEGRA_POWERGATE_XUSBC] = {
.name = "xusbc",
.clk_info = {
[0] = { .clk_name = "xusb_host", .clk_type = CLK_AND_RST },
},
},
};
static atomic_t ref_count_a = ATOMIC_INIT(1); /* for TEGRA_POWERGATE_DISA */
static atomic_t ref_count_b = ATOMIC_INIT(1); /* for TEGRA_POWERGATE_DISB */
static void __iomem *mipi_cal = IO_ADDRESS(TEGRA_MIPI_CAL_BASE);
static u32 mipi_cal_read(unsigned long reg)
{
return readl(mipi_cal + reg);
}
static void mipi_cal_write(u32 val, unsigned long reg)
{
writel_relaxed(val, mipi_cal + reg);
}
#define MC_CLIENT_HOTRESET_CTRL 0x200
#define MC_CLIENT_HOTRESET_STAT 0x204
static DEFINE_SPINLOCK(tegra11x_powergate_lock);
/* Forward Declarations */
int tegra11x_powergate_mc_flush(int id);
int tegra11x_powergate_mc_flush_done(int id);
int tegra11x_unpowergate_partition_with_clk_on(int id);
int tegra11x_powergate_partition_with_clk_off(int id);
bool tegra11x_powergate_check_clamping(int id)
{
u32 mask;
/*
* PCIE and VDE clamping masks are swapped with respect to their
* partition ids
*/
if (id == TEGRA_POWERGATE_VDEC)
mask = (1 << TEGRA_POWERGATE_PCIE);
else if (id == TEGRA_POWERGATE_PCIE)
mask = (1 << TEGRA_POWERGATE_VDEC);
else
mask = (1 << id);
return !!(pmc_read(PWRGATE_CLAMP_STATUS) & mask);
}
#define HOTRESET_READ_COUNT 5
static bool tegra11x_stable_hotreset_check(u32 *stat)
{
int i;
u32 cur_stat;
u32 prv_stat;
unsigned long flags;
spin_lock_irqsave(&tegra11x_powergate_lock, flags);
prv_stat = mc_read(MC_CLIENT_HOTRESET_STAT);
for (i = 0; i < HOTRESET_READ_COUNT; i++) {
cur_stat = mc_read(MC_CLIENT_HOTRESET_STAT);
if (cur_stat != prv_stat) {
spin_unlock_irqrestore(&tegra11x_powergate_lock, flags);
return false;
}
}
*stat = cur_stat;
spin_unlock_irqrestore(&tegra11x_powergate_lock, flags);
return true;
}
/*
* FIXME: sw war for mipi-cal calibration when unpowergating DISA partition
*/
static void tegra11x_mipical_calibrate(int id)
{
struct reg_offset_val {
u32 offset;
u32 por_value;
};
u32 status;
unsigned long flags;
#define MIPI_CAL_MIPI_CAL_CTRL_0 0x0
#define MIPI_CAL_CIL_MIPI_CAL_STATUS_0 0x8
#define MIPI_CAL_CILA_MIPI_CAL_CONFIG_0 0x14
#define MIPI_CAL_CILB_MIPI_CAL_CONFIG_0 0x18
#define MIPI_CAL_CILC_MIPI_CAL_CONFIG_0 0x1c
#define MIPI_CAL_CILD_MIPI_CAL_CONFIG_0 0x20
#define MIPI_CAL_CILE_MIPI_CAL_CONFIG_0 0x24
#define MIPI_CAL_DSIA_MIPI_CAL_CONFIG_0 0x38
#define MIPI_CAL_DSIB_MIPI_CAL_CONFIG_0 0x3c
#define MIPI_CAL_DSIC_MIPI_CAL_CONFIG_0 0x40
#define MIPI_CAL_DSID_MIPI_CAL_CONFIG_0 0x44
static struct reg_offset_val mipi_cal_por_values[] = {
{ MIPI_CAL_MIPI_CAL_CTRL_0, 0x2a000000 },
{ MIPI_CAL_CILA_MIPI_CAL_CONFIG_0, 0x00200000 },
{ MIPI_CAL_CILB_MIPI_CAL_CONFIG_0, 0x00200000 },
{ MIPI_CAL_CILC_MIPI_CAL_CONFIG_0, 0x00200000 },
{ MIPI_CAL_CILD_MIPI_CAL_CONFIG_0, 0x00200000 },
{ MIPI_CAL_CILE_MIPI_CAL_CONFIG_0, 0x00000000 },
{ MIPI_CAL_DSIA_MIPI_CAL_CONFIG_0, 0x00200000 },
{ MIPI_CAL_DSIB_MIPI_CAL_CONFIG_0, 0x00200000 },
{ MIPI_CAL_DSIC_MIPI_CAL_CONFIG_0, 0x00200000 },
{ MIPI_CAL_DSID_MIPI_CAL_CONFIG_0, 0x00200000 },
};
int i;
if (id != TEGRA_POWERGATE_DISA)
return;
spin_lock_irqsave(&tegra11x_powergate_lock, flags);
/* mipi cal por restore */
for (i = 0; i < ARRAY_SIZE(mipi_cal_por_values); i++) {
mipi_cal_write(mipi_cal_por_values[i].por_value,
mipi_cal_por_values[i].offset);
}
/* mipi cal status clear */
status = mipi_cal_read(MIPI_CAL_CIL_MIPI_CAL_STATUS_0);
mipi_cal_write(status, MIPI_CAL_CIL_MIPI_CAL_STATUS_0);
/* mipi cal status read - to flush writes */
status = mipi_cal_read(MIPI_CAL_CIL_MIPI_CAL_STATUS_0);
spin_unlock_irqrestore(&tegra11x_powergate_lock, flags);
}
static int tegra11x_powergate_partition_internal(int id,
struct powergate_partition_info *pg_info)
{
int ret;
if (tegra_powergate_is_powered(id)) {
ret = is_partition_clk_disabled(pg_info);
if (ret < 0) {
/* clock enabled */
ret = tegra11x_powergate_partition_with_clk_off(id);
if (ret < 0)
return ret;
} else {
ret = tegra_powergate_partition(id);
if (ret < 0)
return ret;
}
}
return 0;
}
static int tegra11x_unpowergate_partition_internal(int id,
struct powergate_partition_info *pg_info)
{
int ret;
if (!tegra_powergate_is_powered(id)) {
ret = is_partition_clk_disabled(pg_info);
if (ret) {
/* clock disabled */
ret = tegra11x_unpowergate_partition_with_clk_on(id);
if (ret < 0)
return ret;
} else {
ret = tegra_unpowergate_partition(id);
if (ret < 0)
return ret;
}
}
return 0;
}
/*
* Tegra11x has powergate dependencies between partitions.
* This function captures the dependencies.
*/
static int tegra11x_check_partition_pg_seq(int id,
struct powergate_partition_info *pg_info)
{
int ret;
if (id == TEGRA_POWERGATE_DISA) {
ret = tegra11x_powergate_partition_internal(TEGRA_POWERGATE_VENC,
pg_info);
if (ret < 0)
return ret;
ret = tegra11x_powergate_partition_internal(TEGRA_POWERGATE_DISB,
pg_info);
if (ret < 0)
return ret;
}
return 0;
}
/*
* This function captures power-ungate dependencies between tegra11x partitions
*/
static int tegra11x_check_partition_pug_seq(int id,
struct powergate_partition_info *pg_info)
{
int ret;
switch (id) {
case TEGRA_POWERGATE_DISB:
case TEGRA_POWERGATE_VENC:
ret = tegra11x_unpowergate_partition_internal(TEGRA_POWERGATE_DISA,
pg_info);
if (ret < 0)
return ret;
break;
}
return 0;
}
int tegra11x_powergate_mc_enable(int id)
{
return 0;
}
int tegra11x_powergate_mc_disable(int id)
{
return 0;
}
int tegra11x_powergate_mc_flush(int id)
{
u32 idx, rst_ctrl, rst_stat;
enum mc_client mcClientBit;
unsigned long flags;
bool ret;
for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) {
mcClientBit =
tegra11x_pg_mc_info[id].hot_reset_clients[idx];
if (mcClientBit == MC_CLIENT_LAST)
break;
spin_lock_irqsave(&tegra11x_powergate_lock, flags);
rst_ctrl = mc_read(MC_CLIENT_HOTRESET_CTRL);
rst_ctrl |= (1 << mcClientBit);
mc_write(rst_ctrl, MC_CLIENT_HOTRESET_CTRL);
spin_unlock_irqrestore(&tegra11x_powergate_lock, flags);
do {
udelay(10);
rst_stat = 0;
ret = tegra11x_stable_hotreset_check(&rst_stat);
if (!ret)
continue;
} while (!(rst_stat & (1 << mcClientBit)));
}
return 0;
}
int tegra11x_powergate_mc_flush_done(int id)
{
u32 idx, rst_ctrl;
enum mc_client mcClientBit;
unsigned long flags;
for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) {
mcClientBit =
tegra11x_pg_mc_info[id].hot_reset_clients[idx];
if (mcClientBit == MC_CLIENT_LAST)
break;
spin_lock_irqsave(&tegra11x_powergate_lock, flags);
rst_ctrl = mc_read(MC_CLIENT_HOTRESET_CTRL);
rst_ctrl &= ~(1 << mcClientBit);
mc_write(rst_ctrl, MC_CLIENT_HOTRESET_CTRL);
mc_read(MC_CLIENT_HOTRESET_CTRL);
spin_unlock_irqrestore(&tegra11x_powergate_lock, flags);
}
wmb();
return 0;
}
static int tegra11x_unpowergate(int id,
struct powergate_partition_info *pg_info)
{
int ret;
/* If first clk_ptr is null, fill clk info for the partition */
if (!pg_info->clk_info[0].clk_ptr)
get_clk_info(pg_info);
if (tegra_powergate_is_powered(id))
return tegra_powergate_reset_module(pg_info);
ret = tegra_powergate_set(id, true);
if (ret)
goto err_power;
udelay(10);
powergate_partition_assert_reset(pg_info);
udelay(10);
/* Un-Powergating fails if all clks are not enabled */
ret = partition_clk_enable(pg_info);
if (ret)
goto err_clk_on;
udelay(10);
ret = tegra_powergate_remove_clamping(id);
if (ret)
goto err_clamp;
udelay(10);
tegra11x_mipical_calibrate(id);
powergate_partition_deassert_reset(pg_info);
udelay(10);
tegra_powergate_mc_flush_done(id);
udelay(10);
/* Disable all clks enabled earlier. Drivers should enable clks */
partition_clk_disable(pg_info);
return 0;
err_clamp:
partition_clk_disable(pg_info);
err_clk_on:
powergate_module(id);
err_power:
WARN(1, "Could not Un-Powergate %d", id);
return ret;
}
void tegra11x_powergate_dis_partition(void)
{
tegra1xx_powergate(TEGRA_POWERGATE_DISB,
&tegra11x_powergate_partition_info[TEGRA_POWERGATE_DISB]);
tegra11x_powergate_partition_internal(TEGRA_POWERGATE_VENC,
&tegra11x_powergate_partition_info[TEGRA_POWERGATE_DISA]);
tegra1xx_powergate(TEGRA_POWERGATE_DISA,
&tegra11x_powergate_partition_info[TEGRA_POWERGATE_DISA]);
}
/* The logic manages the ref-count for dis partitions. The dependency between
* disa and disb is hided from client. */
bool tegra11x_powergate_check_dis_refcount(int id, int op)
{
WARN_ONCE(atomic_read(&ref_count_a) < 0, "dis ref a count underflow");
WARN_ONCE(atomic_read(&ref_count_b) < 0, "dis ref b count underflow");
if (op && id == TEGRA_POWERGATE_DISA) {
if (atomic_inc_return(&ref_count_a) != 1)
return 0;
} else if (op && id == TEGRA_POWERGATE_DISB) {
if (tegra_powergate_is_powered(TEGRA_POWERGATE_DISA))
atomic_inc(&ref_count_a);
if (atomic_inc_return(&ref_count_b) != 1)
return 0;
} else if (!op && id == TEGRA_POWERGATE_DISA) {
if (atomic_dec_return(&ref_count_a) != 0)
return 0;
} else if (!op && id == TEGRA_POWERGATE_DISB) {
atomic_dec(&ref_count_a);
if (atomic_dec_return(&ref_count_b) != 0) {
return 0;
} else if (atomic_read(&ref_count_a) == 0) {
tegra11x_powergate_dis_partition();
return 0;
}
}
return 1;
}
int tegra11x_powergate_partition(int id)
{
int ret;
if ((id == TEGRA_POWERGATE_DISA || id == TEGRA_POWERGATE_DISB) &&
!tegra11x_powergate_check_dis_refcount(id, 0))
return 0;
ret = tegra11x_check_partition_pg_seq(id,
&tegra11x_powergate_partition_info[id]);
if (ret)
return ret;
/* call common power-gate API for t1xx */
ret = tegra1xx_powergate(id,
&tegra11x_powergate_partition_info[id]);
return ret;
}
int tegra11x_unpowergate_partition(int id)
{
int ret;
if ((id == TEGRA_POWERGATE_DISA || id == TEGRA_POWERGATE_DISB) &&
!tegra11x_powergate_check_dis_refcount(id, 1))
return 0;
ret = tegra11x_check_partition_pug_seq(id,
&tegra11x_powergate_partition_info[id]);
if (ret)
return ret;
/* t11x needs to calibrate mipi in un-power-gate sequence
* hence it cannot use common un-power-gate api tegra1xx_unpowergate */
ret = tegra11x_unpowergate(id,
&tegra11x_powergate_partition_info[id]);
return ret;
}
int tegra11x_powergate_partition_with_clk_off(int id)
{
return tegraxx_powergate_partition_with_clk_off(id,
&tegra11x_powergate_partition_info[id]);
}
int tegra11x_unpowergate_partition_with_clk_on(int id)
{
return tegraxx_unpowergate_partition_with_clk_on(id,
&tegra11x_powergate_partition_info[id]);
}
const char *tegra11x_get_powergate_domain_name(int id)
{
return tegra11x_powergate_partition_info[id].name;
}
spinlock_t *tegra11x_get_powergate_lock(void)
{
return &tegra11x_powergate_lock;
}
int tegra11x_powergate_init_refcount(void)
{
if (tegra_powergate_is_powered(TEGRA_POWERGATE_DISA))
atomic_set(&ref_count_a, 1);
else
atomic_set(&ref_count_a, 0);
if (tegra_powergate_is_powered(TEGRA_POWERGATE_DISB))
atomic_set(&ref_count_b, 1);
else
atomic_set(&ref_count_b, 0);
return 0;
}
static struct powergate_ops tegra11x_powergate_ops = {
.soc_name = "tegra11x",
.num_powerdomains = TEGRA_NUM_POWERGATE,
.get_powergate_lock = tegra11x_get_powergate_lock,
.get_powergate_domain_name = tegra11x_get_powergate_domain_name,
.powergate_partition = tegra11x_powergate_partition,
.unpowergate_partition = tegra11x_unpowergate_partition,
.powergate_partition_with_clk_off = tegra11x_powergate_partition_with_clk_off,
.unpowergate_partition_with_clk_on = tegra11x_unpowergate_partition_with_clk_on,
.powergate_mc_enable = tegra11x_powergate_mc_enable,
.powergate_mc_disable = tegra11x_powergate_mc_disable,
.powergate_mc_flush = tegra11x_powergate_mc_flush,
.powergate_mc_flush_done = tegra11x_powergate_mc_flush_done,
.powergate_init_refcount = tegra11x_powergate_init_refcount,
.powergate_check_clamping = tegra11x_powergate_check_clamping,
};
struct powergate_ops *tegra11x_powergate_init_chip_support(void)
{
return &tegra11x_powergate_ops;
}