blob: 813f9d9f64c2e7307271a0b2372a6695b7c32f94 [file] [log] [blame]
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <soc/samsung/ect_parser.h>
#include "cmucal.h"
#include "vclk.h"
#include "ra.h"
#include "acpm_dvfs.h"
#include "asv.h"
#define ECT_DUMMY_SFR (0xFFFFFFFF)
unsigned int asv_table_ver = 0;
unsigned int main_rev;
unsigned int sub_rev;
static struct vclk_lut *get_lut(struct vclk *vclk, unsigned int rate)
{
int i;
for (i = 0; i < vclk->num_rates; i++)
if (rate >= vclk->lut[i].rate)
break;
if (i == vclk->num_rates)
return NULL;
return &vclk->lut[i];
}
static unsigned int get_max_rate(unsigned int from, unsigned int to)
{
unsigned int max_rate;
if (from)
max_rate = (from > to) ? from : to;
else
max_rate = to;
return max_rate;
}
static void __select_switch_pll(struct vclk *vclk,
unsigned int rate,
unsigned int select)
{
if (vclk->ops && vclk->ops->switch_pre)
vclk->ops->switch_pre(vclk->vrate, rate);
if (vclk->ops && vclk->ops->switch_trans && select)
vclk->ops->switch_trans(vclk->vrate, rate);
else if (vclk->ops && vclk->ops->restore_trans && !select)
vclk->ops->restore_trans(vclk->vrate, rate);
else
ra_select_switch_pll(vclk->switch_info, select);
if (vclk->ops && vclk->ops->switch_post)
vclk->ops->switch_post(vclk->vrate, rate);
}
static int transition_switch(struct vclk *vclk, struct vclk_lut *lut,
unsigned int switch_rate)
{
unsigned int *list = vclk->list;
unsigned int num_list = vclk->num_list;
/* Change to swithing PLL */
if (vclk->ops && vclk->ops->trans_pre)
vclk->ops->trans_pre(vclk->vrate, lut->rate);
ra_set_clk_by_type(list, lut, num_list, DIV_TYPE, TRANS_HIGH);
__select_switch_pll(vclk, switch_rate, 1);
ra_set_clk_by_type(list, lut, num_list, MUX_TYPE, TRANS_FORCE);
ra_set_clk_by_type(list, lut, num_list, DIV_TYPE, TRANS_LOW);
vclk->vrate = switch_rate;
return 0;
}
static int transition_restore(struct vclk *vclk, struct vclk_lut *lut)
{
unsigned int *list = vclk->list;
unsigned int num_list = vclk->num_list;
/* PLL setting */
ra_set_pll_ops(list, lut, num_list, vclk->ops);
ra_set_clk_by_type(list, lut, num_list, DIV_TYPE, TRANS_HIGH);
__select_switch_pll(vclk, lut->rate, 0);
ra_set_clk_by_type(list, lut, num_list, MUX_TYPE, TRANS_FORCE);
ra_set_clk_by_type(list, lut, num_list, DIV_TYPE, TRANS_LOW);
if (vclk->ops && vclk->ops->trans_post)
vclk->ops->trans_post(vclk->vrate, lut->rate);
return 0;
}
static int transition(struct vclk *vclk,
struct vclk_lut *lut)
{
unsigned int *list = vclk->list;
unsigned int num_list = vclk->num_list;
ra_set_clk_by_type(list, lut, num_list, DIV_TYPE, TRANS_HIGH);
ra_set_clk_by_type(list, lut, num_list, PLL_TYPE, TRANS_LOW);
ra_set_clk_by_type(list, lut, num_list, MUX_TYPE, TRANS_FORCE);
ra_set_clk_by_type(list, lut, num_list, PLL_TYPE, TRANS_HIGH);
ra_set_clk_by_type(list, lut, num_list, DIV_TYPE, TRANS_LOW);
return 0;
}
static bool is_switching_pll_ops(struct vclk *vclk, int cmd)
{
int i;
if (!vclk->switch_info)
return false;
if (cmd != ONESHOT_TRANS)
return true;
for (i = 0; i < vclk->num_list; i++) {
if (IS_PLL(vclk->list[i]))
return true;
}
return false;
}
static int __vclk_set_rate(unsigned int id, unsigned int rate, int cmd)
{
struct vclk *vclk;
struct vclk_lut *new_lut, *switch_lut;
unsigned int switch_rate, max_rate;
if (!IS_VCLK(id))
return ra_set_rate(id, rate);
vclk = cmucal_get_node(id);
if (!vclk || !vclk->lut)
return -EVCLKINVAL;
if (IS_DFS_VCLK(id) || IS_COMMON_VCLK(id))
new_lut = get_lut(vclk, rate);
else
new_lut = get_lut(vclk, rate / 1000);
if (!new_lut)
return -EVCLKINVAL;
if (is_switching_pll_ops(vclk, cmd)) {
switch_lut = new_lut;
switch_rate = rate;
if (is_oneshot_trans(cmd)) {
max_rate = get_max_rate(vclk->vrate, rate);
switch_rate = ra_set_rate_switch(vclk->switch_info,
max_rate);
switch_lut = get_lut(vclk, switch_rate);
if (!switch_lut)
return -EVCLKINVAL;
}
if (is_switch_trans(cmd))
transition_switch(vclk, switch_lut, switch_rate);
if (is_restore_trans(cmd))
transition_restore(vclk, new_lut);
} else if (vclk->seq) {
ra_set_clk_by_seq(vclk->list,
new_lut,
vclk->seq,
vclk->num_list);
} else {
transition(vclk, new_lut);
}
vclk->vrate = rate;
return 0;
}
int vclk_set_rate(unsigned int id, unsigned long rate)
{
int ret;
ret = __vclk_set_rate(id, rate, ONESHOT_TRANS);
return ret;
}
int vclk_set_rate_switch(unsigned int id, unsigned long rate)
{
int ret;
ret = __vclk_set_rate(id, rate, SWITCH_TRANS);
return ret;
}
int vclk_set_rate_restore(unsigned int id, unsigned long rate)
{
int ret;
ret = __vclk_set_rate(id, rate, RESTORE_TRANS);
return ret;
}
unsigned long vclk_recalc_rate(unsigned int id)
{
struct vclk *vclk;
int i, ret;
if (!IS_VCLK(id))
return ra_recalc_rate(id);
vclk = cmucal_get_node(id);
if (!vclk)
return 0;
if (IS_DFS_VCLK(vclk->id) ||
IS_COMMON_VCLK(vclk->id) ||
IS_ACPM_VCLK(vclk->id)) {
for (i = 0; i < vclk->num_rates; i++) {
ret = ra_compare_clk_list(vclk->lut[i].params,
vclk->list,
vclk->num_list);
if (!ret) {
vclk->vrate = vclk->lut[i].rate;
break;
}
}
if (i == vclk->num_rates) {
vclk->vrate = 0;
pr_debug("%s:%x failed\n", __func__, id);
}
} else {
vclk->vrate = ra_recalc_rate(vclk->list[0]);
}
return vclk->vrate;
}
unsigned long vclk_get_rate(unsigned int id)
{
struct vclk *vclk;
if (IS_VCLK(id)) {
vclk = cmucal_get_node(id);
if (vclk)
return vclk->vrate;
}
return 0;
}
int vclk_set_enable(unsigned int id)
{
struct vclk *vclk;
int ret = -EVCLKINVAL;
if (IS_GATE_VCLK(id)) {
vclk = cmucal_get_node(id);
if (vclk)
ret = ra_set_list_enable(vclk->list, vclk->num_list);
} else if (IS_VCLK(id)){
ret = 0;
} else {
ret = ra_set_enable(id, 1);
}
return ret;
}
int vclk_set_disable(unsigned int id)
{
struct vclk *vclk;
int ret = -EVCLKINVAL;
if (IS_GATE_VCLK(id)) {
vclk = cmucal_get_node(id);
if (vclk)
ret = ra_set_list_disable(vclk->list, vclk->num_list);
} else if (IS_VCLK(id)){
ret = 0;
} else {
ret = ra_set_enable(id, 0);
}
return ret;
}
unsigned int vclk_get_lv_num(unsigned int id)
{
struct vclk *vclk;
int lv_num = 0;
vclk = cmucal_get_node(id);
if (vclk && vclk->lut)
lv_num = vclk->num_rates;
return lv_num;
}
unsigned int vclk_get_max_freq(unsigned int id)
{
struct vclk *vclk;
int rate = 0;
vclk = cmucal_get_node(id);
if (vclk && vclk->lut)
rate = vclk->max_freq;
return rate;
}
unsigned int vclk_get_min_freq(unsigned int id)
{
struct vclk *vclk;
int rate = 0;
vclk = cmucal_get_node(id);
if (vclk && vclk->lut)
rate = vclk->min_freq;
return rate;
}
int vclk_get_rate_table(unsigned int id, unsigned long *table)
{
struct vclk *vclk;
int i;
unsigned int nums = 0;
vclk = cmucal_get_node(id);
if (!vclk || !IS_VCLK(vclk->id))
return 0;
if (vclk->lut) {
for (i = 0; i < vclk->num_rates; i++)
table[i] = vclk->lut[i].rate;
nums = vclk->num_rates;
}
return nums;
}
int vclk_get_bigturbo_table(unsigned int *table)
{
void *gen_block;
struct ect_gen_param_table *bigturbo;
int idx;
int i;
gen_block = ect_get_block("GEN");
if (gen_block == NULL)
return -EVCLKINVAL;
bigturbo = ect_gen_param_get_table(gen_block, "BIGTURBO");
if (bigturbo == NULL)
return -EVCLKINVAL;
if (bigturbo->num_of_row == 0)
return -EVCLKINVAL;
if (asv_table_ver >= bigturbo->num_of_row)
idx = bigturbo->num_of_row - 1;
else
idx = asv_table_ver;
for (i = 0; i < bigturbo->num_of_col; i++)
table[i] = bigturbo->parameter[idx * bigturbo->num_of_col + i];
return 0;
}
unsigned int vclk_get_boot_freq(unsigned int id)
{
struct vclk *vclk;
unsigned int rate = 0;
vclk = cmucal_get_node(id);
if (!vclk || !(IS_DFS_VCLK(vclk->id) || IS_ACPM_VCLK(vclk->id)))
return rate;
if (vclk->boot_freq)
rate = vclk->boot_freq;
else
rate = (unsigned int)vclk_recalc_rate(id);
return rate;
}
unsigned int vclk_get_resume_freq(unsigned int id)
{
struct vclk *vclk;
unsigned int rate = 0;
vclk = cmucal_get_node(id);
if (!vclk || !(IS_DFS_VCLK(vclk->id) || IS_ACPM_VCLK(vclk->id)))
return rate;
if (vclk->resume_freq)
rate = vclk->resume_freq;
else
rate = (unsigned int)vclk_recalc_rate(id);
return rate;
}
static int vclk_get_dfs_info(struct vclk *vclk)
{
int i, j;
void *dvfs_block;
struct ect_dvfs_domain *dvfs_domain;
void *gen_block;
struct ect_gen_param_table *minmax = NULL;
unsigned int *minmax_table = NULL;
int *params, idx;
int ret = 0;
char buf[32];
dvfs_block = ect_get_block("DVFS");
if (dvfs_block == NULL)
return -EVCLKNOENT;
dvfs_domain = ect_dvfs_get_domain(dvfs_block, vclk->name);
if (dvfs_domain == NULL)
return -EVCLKINVAL;
gen_block = ect_get_block("GEN");
if (gen_block) {
sprintf(buf, "MINMAX_%s", vclk->name);
minmax = ect_gen_param_get_table(gen_block, buf);
if (minmax != NULL) {
for (i = 0; i < minmax->num_of_row; i++) {
minmax_table = &minmax->parameter[minmax->num_of_col * i];
if (minmax_table[0] == asv_table_ver)
break;
}
}
}
vclk->num_rates = dvfs_domain->num_of_level;
vclk->num_list = dvfs_domain->num_of_clock;
vclk->max_freq = dvfs_domain->max_frequency;
vclk->min_freq = dvfs_domain->min_frequency;
if (minmax_table != NULL) {
vclk->min_freq = minmax_table[MINMAX_MIN_FREQ] * 1000;
vclk->max_freq = minmax_table[MINMAX_MAX_FREQ] * 1000;
}
pr_debug("ACPM_DVFS :%s\n", vclk->name);
vclk->list = kzalloc(sizeof(unsigned int) * vclk->num_list, GFP_KERNEL);
if (!vclk->list)
return -EVCLKNOMEM;
vclk->lut = kzalloc(sizeof(struct vclk_lut) * vclk->num_rates,
GFP_KERNEL);
if (!vclk->lut) {
ret = -EVCLKNOMEM;
goto err_nomem1;
}
for (i = 0; i < vclk->num_rates; i++) {
vclk->lut[i].rate = dvfs_domain->list_level[i].level;
params = kcalloc(vclk->num_list, sizeof(int), GFP_KERNEL);
if (!params) {
ret = -EVCLKNOMEM;
if (i == 0)
goto err_nomem2;
for (i = i-1; i >= 0; i--)
kfree(vclk->lut[i].params);
goto err_nomem2;
}
for (j = 0; j < vclk->num_list; ++j) {
idx = i * vclk->num_list + j;
params[j] = dvfs_domain->list_dvfs_value[idx];
}
vclk->lut[i].params = params;
}
vclk->boot_freq = 0;
vclk->resume_freq = 0;
if (minmax_table != NULL) {
for (i = 0; i < vclk->num_rates; i++) {
if (vclk->lut[i].rate == minmax_table[MINMAX_BOOT_FREQ] * 1000)
vclk->boot_freq = vclk->lut[i].rate;
}
for (i = 0; i < vclk->num_rates; i++) {
if (vclk->lut[i].rate == minmax_table[MINMAX_RESUME_FREQ] * 1000)
vclk->resume_freq = vclk->lut[i].rate;
}
} else{
if (dvfs_domain->boot_level_idx != -1)
vclk->boot_freq = vclk->lut[dvfs_domain->boot_level_idx].rate;
if (dvfs_domain->resume_level_idx != -1)
vclk->resume_freq = vclk->lut[dvfs_domain->resume_level_idx].rate;
}
return ret;
err_nomem2:
kfree(vclk->lut);
err_nomem1:
kfree(vclk->list);
return ret;
}
static struct ect_voltage_table *get_max_min_freq_lv(struct ect_voltage_domain *domain, unsigned int version, int *max_lv, int *min_lv)
{
int i;
unsigned int max_asv_version = 0;
struct ect_voltage_table *table = NULL;
for (i = 0; i < domain->num_of_table; i++) {
table = &domain->table_list[i];
if (version == table->table_version)
break;
if (table->table_version > max_asv_version)
max_asv_version = table->table_version;
}
if (i == domain->num_of_table) {
pr_err("There is no voltage table, force change %d to %d\n",
asv_table_ver, max_asv_version);
asv_table_ver = max_asv_version;
}
if (!table) {
*max_lv = -1;
*min_lv = -1;
return NULL;
}
*max_lv = -1;
*min_lv = domain->num_of_level - 1;
for (i = 0; i < domain->num_of_level; i++) {
if (*max_lv == -1 && table->level_en[i])
*max_lv = i;
if (*max_lv != -1 && !table->level_en[i]) {
*min_lv = i - 1;
break;
}
}
return table;
}
static int vclk_get_asv_info(struct vclk *vclk)
{
void *asv_block;
struct ect_voltage_domain *domain;
struct ect_voltage_table *table = NULL;
int max_lv, min_lv;
int ret = 0;
char buf[32];
void *gen_block;
struct ect_gen_param_table *minmax = NULL;
asv_block = ect_get_block("ASV");
if (asv_block == NULL)
return -EVCLKNOENT;
domain = ect_asv_get_domain(asv_block, vclk->name);
if (domain == NULL)
return -EVCLKINVAL;
gen_block = ect_get_block("GEN");
if (gen_block) {
sprintf(buf, "MINMAX_%s", vclk->name);
minmax = ect_gen_param_get_table(gen_block, buf);
if (minmax != NULL)
goto minmax_skip;
}
table = get_max_min_freq_lv(domain, asv_table_ver, &max_lv, &min_lv);
if (table == NULL)
return -EVCLKFAULT;
if (max_lv >= 0)
vclk->max_freq = domain->level_list[max_lv] * 1000;
else
vclk->max_freq = -1;
if (min_lv >= 0)
vclk->min_freq = domain->level_list[min_lv] * 1000;
else
vclk->min_freq = -1;
if (table->boot_level_idx >= 0)
vclk->boot_freq = domain->level_list[table->boot_level_idx] * 1000;
else
vclk->boot_freq = -1;
if (table->resume_level_idx >= 0)
vclk->resume_freq = domain->level_list[table->resume_level_idx] * 1000;
else
vclk->resume_freq = -1;
minmax_skip:
pr_debug(" num_rates : %7d\n", vclk->num_rates);
pr_debug(" num_clk_list : %7d\n", vclk->num_list);
pr_debug(" max_freq : %7d\n", vclk->max_freq);
pr_debug(" min_freq : %7d\n", vclk->min_freq);
pr_debug(" boot_freq : %7d\n", vclk->boot_freq);
pr_debug(" resume_freq : %7d\n", vclk->resume_freq);
return ret;
}
static void vclk_bind(void)
{
struct vclk *vclk;
int i;
bool warn_on = 0;
int ret;
for (i = 0; i < cmucal_get_list_size(ACPM_VCLK_TYPE); i++) {
vclk = cmucal_get_node(ACPM_VCLK_TYPE | i);
if (!vclk) {
pr_err("cannot found vclk node %x\n", i);
continue;
}
ret = vclk_get_dfs_info(vclk);
if (ret == -EVCLKNOENT) {
if (!warn_on)
pr_warn("ECT DVFS not found\n");
warn_on = 1;
} else if (ret) {
pr_err("ECT DVFS [%s] not found %d\n",
vclk->name, ret);
} else {
ret = vclk_get_asv_info(vclk);
if (ret)
pr_err("ECT ASV [%s] not found %d\n",
vclk->name, ret);
}
}
}
int vclk_register_ops(unsigned int id, struct vclk_trans_ops *ops)
{
struct vclk *vclk;
if (IS_DFS_VCLK(id)) {
vclk = cmucal_get_node(id);
if (!vclk)
return -EVCLKINVAL;
vclk->ops = ops;
return 0;
}
return -EVCLKNOENT;
}
int __init vclk_initialize(void)
{
pr_info("vclk initialize for cmucal\n");
ra_init();
asv_table_ver = asv_table_init();
id_get_rev(&main_rev, &sub_rev);
vclk_bind();
return 0;
}