blob: 2bd90858b562c9f75f8dc72addf50cea6e6f2e6f [file] [log] [blame]
/*
* Common function for power&freq support
*
* Copyright (C) 2013 Marvell International Ltd. All rights reserved.
*
* Author: Lucao <lucao@marvell.com>
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <power/pxa_common.h>
static void __clk_wait_fc_done(void *address, unsigned long fcreq_bit)
{
/* fc done should be asserted in several clk cycles */
unsigned int i = 50;
udelay(2);
while ((__raw_readl(address) & fcreq_bit) && i) {
udelay(10);
i--;
}
if (i == 0) {
printf("CLK fc req failed! reg = 0x%x, fc_req = 0x%x\n",
(unsigned int)__raw_readl(address),
(unsigned int)fcreq_bit);
}
}
unsigned int peri_get_parent_index(const char *parent,
struct periph_parents *table, int size)
{
int i;
for (i = 0; i < size; i++) {
if (!strcmp(parent, table[i].name))
break;
}
if (i == size) {
printf("no supported source parent found\n");
return 0;
}
return i;
}
void peri_set_mux_div(struct peri_reg_info *reg_info,
unsigned long mux, unsigned long div)
{
unsigned int regval;
unsigned int muxmask, divmask;
unsigned int muxval, divval, fcreqmsk = 0xFFFFFFFF;
muxval = mux & reg_info->src_sel_mask;
divval = div & reg_info->div_mask;
muxval = muxval << reg_info->src_sel_shift;
divval = divval << reg_info->div_shift;
muxmask = reg_info->src_sel_mask << reg_info->src_sel_shift;
divmask = reg_info->div_mask << reg_info->div_shift;
regval = __raw_readl(reg_info->reg_addr);
regval &= ~(muxmask | divmask);
regval |= (muxval | divval);
if (reg_info->fcreq_mask) {
fcreqmsk = reg_info->fcreq_mask << reg_info->fcreq_shift;
regval |= fcreqmsk;
}
__raw_writel(regval, reg_info->reg_addr);
if (reg_info->fcreq_mask)
__clk_wait_fc_done(reg_info->reg_addr, fcreqmsk);
regval = __raw_readl(reg_info->reg_addr);
printf("regval[%lx] = %x\n", (unsigned long)reg_info->reg_addr, regval);
}
int peri_set_clk(unsigned long freq, struct periph_clk_tbl *tbl,
int tbl_size, struct peri_reg_info *clk_reg_info,
struct periph_parents *periph_parents, int parent_size)
{
unsigned long rate, mux, div;
unsigned int parent, i;
rate = freq * MHZ;
for (i = 0; i < tbl_size; i++) {
if (rate <= tbl[i].fclk_rate) {
printf("i = %d\n", i);
printf("set clk rate @ %lu\n", tbl[i].fclk_rate);
break;
}
}
if (i == tbl_size) {
printf("no supported rate\n");
return -1;
}
parent = peri_get_parent_index(tbl[i].fclk_parent, periph_parents,
parent_size);
mux = periph_parents[parent].hw_sel;
div = (periph_parents[parent].rate / tbl[i].fclk_rate) - 1;
peri_set_mux_div(clk_reg_info, mux, div);
return 0;
}
int peri_setrate(unsigned long freq, struct periph_clk_tbl *tbl,
int tbl_size, struct peri_reg_info *aclk_reg_info,
struct peri_reg_info *fclk_reg_info,
struct periph_parents *periph_aparents, int aparent_size,
struct periph_parents *periph_fparents, int fparent_size)
{
unsigned long rate, fmux, amux, fdiv, adiv;
unsigned int fparent, aparent, i;
rate = freq * MHZ;
for (i = 0; i < tbl_size; i++) {
if (rate <= tbl[i].fclk_rate) {
printf("i = %d\n", i);
printf("set fclk rate @ %lu aclk rate @ %lu\n",
tbl[i].fclk_rate, tbl[i].aclk_rate);
break;
}
}
if (i == tbl_size) {
printf("no supported rate\n");
return -1;
}
aparent = peri_get_parent_index(tbl[i].aclk_parent, periph_aparents,
aparent_size);
amux = periph_aparents[aparent].hw_sel;
adiv = (periph_aparents[aparent].rate / tbl[i].aclk_rate) - 1;
peri_set_mux_div(aclk_reg_info, amux, adiv);
fparent = peri_get_parent_index(tbl[i].fclk_parent, periph_fparents,
fparent_size);
fmux = periph_fparents[fparent].hw_sel;
fdiv = (periph_fparents[fparent].rate / tbl[i].fclk_rate) - 1;
peri_set_mux_div(fclk_reg_info, fmux, fdiv);
return 0;
}
/* power domain related */
enum pd_island {
GC3D,
GC2D,
VPU,
ISP,
ISLAND_MAX
};
struct pd_common_data {
u32 *reg_clk_res_ctrl;
u32 bit_hw_mode;
u32 bit_auto_pwr_on;
u32 bit_pwr_stat;
};
struct pd_common_data pd_data[ISLAND_MAX] = {
[GC3D] = {
.reg_clk_res_ctrl = (u32 *)0xd42828cc,
.bit_hw_mode = 11,
.bit_auto_pwr_on = 0,
.bit_pwr_stat = 0,
},
[GC2D] = {
.reg_clk_res_ctrl = (u32 *)0xd42828f4,
.bit_hw_mode = 11,
.bit_auto_pwr_on = 6,
.bit_pwr_stat = 6,
},
[VPU] = {
.reg_clk_res_ctrl = (u32 *)0xd42828a4,
.bit_hw_mode = 19,
.bit_auto_pwr_on = 2,
.bit_pwr_stat = 2,
},
[ISP] = {
.reg_clk_res_ctrl = (u32 *)0xd4282838,
.bit_hw_mode = 15,
.bit_auto_pwr_on = 4,
.bit_pwr_stat = 4,
},
};
/* always use hw control */
static int island_poweron(enum pd_island island)
{
u32 val;
int loop = 5000;
struct pd_common_data *data = &pd_data[island];
/* set HW on/off mode */
val = __raw_readl(data->reg_clk_res_ctrl);
val |= (1 << data->bit_hw_mode);
__raw_writel(val, data->reg_clk_res_ctrl);
/* on1, on2, off timer */
__raw_writel(0x20001fff, APMU_PWR_BLK_TMR_REG);
/* auto power on */
val = __raw_readl(APMU_PWR_CTRL_REG);
val |= (1 << data->bit_auto_pwr_on);
__raw_writel(val, APMU_PWR_CTRL_REG);
/*
* power on takes 316us
*/
udelay(320);
/* polling PWR_STAT bit */
for (loop = 5000; loop > 0; loop--) {
val = __raw_readl(APMU_PWR_STATUS_REG);
if (val & (1 << data->bit_pwr_stat))
break;
udelay(1);
}
if (loop <= 0) {
printf("island %d failed to power on!\n", island);
return -1;
}
return 0;
}
/* always use hw control */
static int island_poweroff(enum pd_island island)
{
u32 val;
int loop = 5000;
struct pd_common_data *data = &pd_data[island];
/* auto power off */
val = __raw_readl(APMU_PWR_CTRL_REG);
val &= ~(1 << data->bit_auto_pwr_on);
__raw_writel(val, APMU_PWR_CTRL_REG);
/*
* power off takes 23us, add a pre-delay to reduce the
* number of polling
*/
udelay(20);
/* polling PWR_STAT bit */
for (loop = 5000; loop > 0; loop--) {
val = __raw_readl(APMU_PWR_STATUS_REG);
if (!(val & (1 << data->bit_pwr_stat)))
break;
udelay(1);
}
if (loop <= 0) {
printf("island %d failed to power off!\n", island);
return -1;
}
return 0;
}
static int island_pwr(enum pd_island island, u32 onoff)
{
int ret = 0;
if (onoff)
ret = island_poweron(island);
else
ret = island_poweroff(island);
return ret;
};
static int do_gcpwr(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong onoff;
int res = -1;
if (argc != 2) {
printf("usage: gcpwr 1/0\n");
return -1;
}
res = strict_strtoul(argv[1], 0, &onoff);
if (res == 0)
res = island_pwr(GC3D, onoff);
return res;
}
U_BOOT_CMD(
gc_pwr, 6, 1, do_gcpwr,
"Gc power on/off",
""
);
static int do_gc2dpwr(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong onoff;
int res = -1;
if (argc != 2) {
printf("usage: gc2d_pwr 1/0\n");
return -1;
}
res = strict_strtoul(argv[1], 0, &onoff);
if (res == 0)
res = island_pwr(GC2D, onoff);
return res;
}
U_BOOT_CMD(
gc2d_pwr, 6, 1, do_gc2dpwr,
"Gc2D power on/off",
""
);
static int do_vpupwr(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong onoff;
int res = -1;
if (argc != 2) {
printf("usage: vpu_pwr 1/0\n");
return -1;
}
res = strict_strtoul(argv[1], 0, &onoff);
if (res == 0)
res = island_pwr(VPU, onoff);
return res;
}
U_BOOT_CMD(
vpu_pwr, 6, 1, do_vpupwr,
"VPU power on/off",
""
);
static int do_isppwr(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong onoff;
int res = -1;
if (argc != 2) {
printf("usage: isp_pwr 1/0\n");
return -1;
}
res = strict_strtoul(argv[1], 0, &onoff);
if (res == 0)
res = island_pwr(ISP, onoff);
return res;
}
U_BOOT_CMD(
isp_pwr, 6, 1, do_isppwr,
"ISP power on/off",
""
);
/* GC internal scaling and clock gating feature support */
enum GC_COMP {
GC3D_CTR,
GC2D_CTR,
};
#define GC3D_BASE (0xc0400000)
#define GC2D_BASE (0xd420c000)
#define REG_GCPULSEEATER (GC3D_BASE + 0x10c)
#define AQHICLKCTR_OFF (0x000)
#define POWERCTR_OFF (0x100)
#define FSCALE_VALMASK (0x1fc)
#define SHADSCALE_VALMASK (0xfe)
u32 *gcctr_base[] = {
(u32 *)GC3D_BASE, (u32 *)GC2D_BASE
};
static void gc_fs_on(enum GC_COMP comp)
{
u32 val;
/* switch to manual mode, only 3D has this extra step */
if (GC3D_CTR == comp) {
val = __raw_readl(REG_GCPULSEEATER);
val &= ~(1 << 16);
val |= (1 << 17);
__raw_writel(val, REG_GCPULSEEATER);
/* shader scaling to 1/64 */
val |= (1 << 0);
val &= ~(SHADSCALE_VALMASK);
val |= (1 << 1);
__raw_writel(val, REG_GCPULSEEATER);
val &= ~(1 << 0);
__raw_writel(val, REG_GCPULSEEATER);
}
val = __raw_readl(gcctr_base[comp] + AQHICLKCTR_OFF);
val &= ~(FSCALE_VALMASK);
/* set 1/64 scaling divider */
val |= (1 << 2) | (1 << 9);
__raw_writel(val, gcctr_base[comp] + AQHICLKCTR_OFF);
/* make scaling work */
val &= ~(1 << 9);
__raw_writel(val, gcctr_base[comp] + AQHICLKCTR_OFF);
}
static void gc_fs_off(enum GC_COMP comp)
{
u32 val;
/* switch to auto mode, only 3D has this extra step */
if (GC3D_CTR == comp) {
val = __raw_readl(REG_GCPULSEEATER);
val &= ~(1 << 16);
val |= (1 << 17);
__raw_writel(val, REG_GCPULSEEATER);
/* shader scaling to 64/64 */
val |= (1 << 0);
val &= ~(SHADSCALE_VALMASK);
val |= (0x40 << 1);
__raw_writel(val, REG_GCPULSEEATER);
val &= ~(1 << 0);
__raw_writel(val, REG_GCPULSEEATER);
}
val = __raw_readl(gcctr_base[comp] + AQHICLKCTR_OFF);
val &= ~(FSCALE_VALMASK);
/* set 64/64 scaling divider */
val |= (64 << 2) | (1 << 9);
__raw_writel(val, gcctr_base[comp] + AQHICLKCTR_OFF);
/* make scaling work */
val &= ~(1 << 9);
__raw_writel(val, gcctr_base[comp] + AQHICLKCTR_OFF);
}
static void gc_cg_ctrl(enum GC_COMP comp, u32 onoff)
{
u32 val;
val = __raw_readl(gcctr_base[comp] + POWERCTR_OFF);
val &= ~(1 << 0);
val |= (!!onoff);
__raw_writel(val, gcctr_base[comp] + POWERCTR_OFF);
}
static int do_enable_3dgc_fs(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong onoff;
int res = -1;
if (argc != 2) {
printf("usage: enable_3dgc_fs 1/0\n");
return res;
}
res = strict_strtoul(argv[1], 0, &onoff);
if (res == 0) {
if (onoff)
gc_fs_on(GC3D_CTR);
else
gc_fs_off(GC3D_CTR);
}
return res;
}
U_BOOT_CMD(
enable_3dgc_fs, 6, 1, do_enable_3dgc_fs,
"Enable GC3D frequency scaling",
""
);
static int do_enable_3dgc_cg(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong onoff;
int res = -1;
if (argc != 2) {
printf("usage: enable_3dgc_cg 1/0\n");
return res;
}
res = strict_strtoul(argv[1], 0, &onoff);
if (res == 0)
gc_cg_ctrl(GC3D_CTR, onoff);
return res;
}
U_BOOT_CMD(
enable_3dgc_cg, 6, 1, do_enable_3dgc_cg,
"Enable GC3D clock gating",
""
);
static int do_enable_2dgc_fs(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong onoff;
int res = -1;
if (argc != 2) {
printf("usage: enable_2dgc_fs 1/0\n");
return res;
}
res = strict_strtoul(argv[1], 0, &onoff);
if (res == 0) {
if (onoff)
gc_fs_on(GC2D_CTR);
else
gc_fs_off(GC2D_CTR);
}
return res;
}
U_BOOT_CMD(
enable_2dgc_fs, 6, 1, do_enable_2dgc_fs,
"Enable GC2D frequency scaling",
""
);
static int do_enable_2dgc_cg(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong onoff;
int res = -1;
if (argc != 2) {
printf("usage: enable_2dgc_cg 1/0\n");
return res;
}
res = strict_strtoul(argv[1], 0, &onoff);
if (res == 0)
gc_cg_ctrl(GC2D_CTR, onoff);
return res;
}
U_BOOT_CMD(
enable_2dgc_cg, 6, 1, do_enable_2dgc_cg,
"Enable GC2D clock gating",
""
);