soc: qcom: cpr: Move common functions to new file
In preparation for implementing a new driver that will be handling CPRv3, CPRv4 and CPR-Hardened, format out common functions to a new file. Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org> (JAMI: fixup for 5.15 & 5.17)
This commit is contained in:
parent
a308847c12
commit
e17fc9fe80
4 changed files with 490 additions and 382 deletions
|
|
@ -3,7 +3,7 @@ CFLAGS_rpmh-rsc.o := -I$(src)
|
|||
obj-$(CONFIG_QCOM_AOSS_QMP) += qcom_aoss.o
|
||||
obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o
|
||||
obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
|
||||
obj-$(CONFIG_QCOM_CPR) += cpr.o
|
||||
obj-$(CONFIG_QCOM_CPR) += cpr-common.o cpr.o
|
||||
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
|
||||
obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o
|
||||
obj-$(CONFIG_QCOM_OCMEM) += ocmem.o
|
||||
|
|
|
|||
349
drivers/soc/qcom/cpr-common.c
Normal file
349
drivers/soc/qcom/cpr-common.c
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2019, Linaro Limited
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include "cpr-common.h"
|
||||
|
||||
int cpr_populate_ring_osc_idx(struct device *dev,
|
||||
struct fuse_corner *fuse_corner,
|
||||
const struct cpr_fuse *cpr_fuse,
|
||||
int num_fuse_corners)
|
||||
{
|
||||
struct fuse_corner *end = fuse_corner + num_fuse_corners;
|
||||
u32 data;
|
||||
int ret;
|
||||
|
||||
for (; fuse_corner < end; fuse_corner++, cpr_fuse++) {
|
||||
ret = nvmem_cell_read_variable_le_u32(dev, cpr_fuse->ring_osc, &data);
|
||||
if (ret)
|
||||
return ret;
|
||||
fuse_corner->ring_osc_idx = data;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cpr_read_fuse_uV(int init_v_width, int step_size_uV, int ref_uV,
|
||||
int adj, int step_volt, const char *init_v_efuse,
|
||||
struct device *dev)
|
||||
{
|
||||
int steps, uV;
|
||||
u32 bits = 0;
|
||||
int ret;
|
||||
|
||||
ret = nvmem_cell_read_variable_le_u32(dev, init_v_efuse, &bits);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
steps = bits & (BIT(init_v_width - 1) - 1);
|
||||
/* Not two's complement.. instead highest bit is sign bit */
|
||||
if (bits & BIT(init_v_width - 1))
|
||||
steps = -steps;
|
||||
|
||||
uV = ref_uV + steps * step_size_uV;
|
||||
|
||||
/* Apply open-loop fixed adjustments to fused values */
|
||||
uV += adj;
|
||||
|
||||
return DIV_ROUND_UP(uV, step_volt) * step_volt;
|
||||
}
|
||||
|
||||
const struct cpr_fuse *cpr_get_fuses(struct device *dev, int tid,
|
||||
int num_fuse_corners)
|
||||
{
|
||||
struct cpr_fuse *fuses;
|
||||
int i;
|
||||
|
||||
fuses = devm_kcalloc(dev, num_fuse_corners,
|
||||
sizeof(struct cpr_fuse),
|
||||
GFP_KERNEL);
|
||||
if (!fuses)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (i = 0; i < num_fuse_corners; i++) {
|
||||
char tbuf[50];
|
||||
|
||||
snprintf(tbuf, sizeof(tbuf), "cpr%d_ring_osc%d", tid, i + 1);
|
||||
fuses[i].ring_osc = devm_kstrdup(dev, tbuf, GFP_KERNEL);
|
||||
if (!fuses[i].ring_osc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
snprintf(tbuf, sizeof(tbuf),
|
||||
"cpr%d_init_voltage%d", tid, i + 1);
|
||||
fuses[i].init_voltage = devm_kstrdup(dev, tbuf,
|
||||
GFP_KERNEL);
|
||||
if (!fuses[i].init_voltage)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
snprintf(tbuf, sizeof(tbuf), "cpr%d_quotient%d", tid, i + 1);
|
||||
fuses[i].quotient = devm_kstrdup(dev, tbuf, GFP_KERNEL);
|
||||
if (!fuses[i].quotient)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
snprintf(tbuf, sizeof(tbuf),
|
||||
"cpr%d_quotient_offset%d", tid, i + 1);
|
||||
fuses[i].quotient_offset = devm_kstrdup(dev, tbuf,
|
||||
GFP_KERNEL);
|
||||
if (!fuses[i].quotient_offset)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
return fuses;
|
||||
}
|
||||
|
||||
int cpr_populate_fuse_common(struct device *dev,
|
||||
struct fuse_corner_data *fdata,
|
||||
const struct cpr_fuse *cpr_fuse,
|
||||
struct fuse_corner *fuse_corner,
|
||||
int step_volt, int init_v_width,
|
||||
int init_v_step)
|
||||
{
|
||||
int uV, ret;
|
||||
|
||||
/* Populate uV */
|
||||
uV = cpr_read_fuse_uV(init_v_width, init_v_step,
|
||||
fdata->ref_uV, fdata->volt_oloop_adjust,
|
||||
step_volt, cpr_fuse->init_voltage, dev);
|
||||
if (uV < 0)
|
||||
return uV;
|
||||
|
||||
/*
|
||||
* Update SoC voltages: platforms might choose a different
|
||||
* regulators than the one used to characterize the algorithms
|
||||
* (ie, init_voltage_step).
|
||||
*/
|
||||
fdata->min_uV = roundup(fdata->min_uV, step_volt);
|
||||
fdata->max_uV = roundup(fdata->max_uV, step_volt);
|
||||
|
||||
fuse_corner->min_uV = fdata->min_uV;
|
||||
fuse_corner->max_uV = fdata->max_uV;
|
||||
fuse_corner->uV = clamp(uV, fuse_corner->min_uV, fuse_corner->max_uV);
|
||||
|
||||
/* Populate target quotient by scaling */
|
||||
ret = nvmem_cell_read_variable_le_u32(dev, cpr_fuse->quotient, &fuse_corner->quot);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
fuse_corner->quot *= fdata->quot_scale;
|
||||
fuse_corner->quot += fdata->quot_offset;
|
||||
fuse_corner->quot += fdata->quot_adjust;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns: Index of the initial corner or negative number for error.
|
||||
*/
|
||||
int cpr_find_initial_corner(struct device *dev, struct clk *cpu_clk,
|
||||
struct corner *corners, int num_corners)
|
||||
{
|
||||
unsigned long rate;
|
||||
struct corner *iter, *corner;
|
||||
const struct corner *end;
|
||||
unsigned int ret = 0;
|
||||
|
||||
if (!cpu_clk)
|
||||
return -EINVAL;
|
||||
|
||||
end = &corners[num_corners - 1];
|
||||
rate = clk_get_rate(cpu_clk);
|
||||
|
||||
/*
|
||||
* Some bootloaders set a CPU clock frequency that is not defined
|
||||
* in the OPP table. When running at an unlisted frequency,
|
||||
* cpufreq_online() will change to the OPP which has the lowest
|
||||
* frequency, at or above the unlisted frequency.
|
||||
* Since cpufreq_online() always "rounds up" in the case of an
|
||||
* unlisted frequency, this function always "rounds down" in case
|
||||
* of an unlisted frequency. That way, when cpufreq_online()
|
||||
* triggers the first ever call to cpr_set_performance_state(),
|
||||
* it will correctly determine the direction as UP.
|
||||
*/
|
||||
for (iter = corners; iter <= end; iter++) {
|
||||
if (iter->freq > rate)
|
||||
break;
|
||||
ret++;
|
||||
if (iter->freq == rate) {
|
||||
corner = iter;
|
||||
break;
|
||||
}
|
||||
if (iter->freq < rate)
|
||||
corner = iter;
|
||||
}
|
||||
|
||||
if (!corner) {
|
||||
dev_err(dev, "boot up corner not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "boot up perf state: %u\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 cpr_get_fuse_corner(struct dev_pm_opp *opp, u32 tid)
|
||||
{
|
||||
struct device_node *np;
|
||||
u32 fc;
|
||||
|
||||
np = dev_pm_opp_get_of_node(opp);
|
||||
if (of_property_read_u32_index(np, "qcom,opp-fuse-level", tid, &fc)) {
|
||||
pr_debug("%s: missing 'qcom,opp-fuse-level' property\n",
|
||||
__func__);
|
||||
fc = 0;
|
||||
}
|
||||
|
||||
of_node_put(np);
|
||||
|
||||
return fc;
|
||||
}
|
||||
|
||||
unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
|
||||
struct device *cpu_dev)
|
||||
{
|
||||
u64 rate = 0;
|
||||
struct device_node *ref_np;
|
||||
struct device_node *desc_np;
|
||||
struct device_node *child_np = NULL;
|
||||
struct device_node *child_req_np = NULL;
|
||||
|
||||
desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
|
||||
if (!desc_np)
|
||||
return 0;
|
||||
|
||||
ref_np = dev_pm_opp_get_of_node(ref);
|
||||
if (!ref_np)
|
||||
goto out_ref;
|
||||
|
||||
do {
|
||||
of_node_put(child_req_np);
|
||||
child_np = of_get_next_available_child(desc_np, child_np);
|
||||
child_req_np = of_parse_phandle(child_np, "required-opps", 0);
|
||||
} while (child_np && child_req_np != ref_np);
|
||||
|
||||
if (child_np && child_req_np == ref_np)
|
||||
of_property_read_u64(child_np, "opp-hz", &rate);
|
||||
|
||||
of_node_put(child_req_np);
|
||||
of_node_put(child_np);
|
||||
of_node_put(ref_np);
|
||||
out_ref:
|
||||
of_node_put(desc_np);
|
||||
|
||||
return (unsigned long) rate;
|
||||
}
|
||||
|
||||
int cpr_calculate_scaling(const char *quot_offset,
|
||||
struct device *dev,
|
||||
const struct fuse_corner_data *fdata,
|
||||
const struct corner *corner)
|
||||
{
|
||||
u32 quot_diff = 0;
|
||||
unsigned long freq_diff;
|
||||
int scaling;
|
||||
const struct fuse_corner *fuse, *prev_fuse;
|
||||
int ret;
|
||||
|
||||
fuse = corner->fuse_corner;
|
||||
prev_fuse = fuse - 1;
|
||||
|
||||
if (quot_offset) {
|
||||
ret = nvmem_cell_read_variable_le_u32(dev, quot_offset, "_diff);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
quot_diff *= fdata->quot_offset_scale;
|
||||
quot_diff += fdata->quot_offset_adjust;
|
||||
} else {
|
||||
quot_diff = fuse->quot - prev_fuse->quot;
|
||||
}
|
||||
|
||||
freq_diff = fuse->max_freq - prev_fuse->max_freq;
|
||||
freq_diff /= 1000000; /* Convert to MHz */
|
||||
scaling = 1000 * quot_diff / freq_diff;
|
||||
return min(scaling, fdata->max_quot_scale);
|
||||
}
|
||||
|
||||
int cpr_interpolate(const struct corner *corner, int step_volt,
|
||||
const struct fuse_corner_data *fdata)
|
||||
{
|
||||
unsigned long f_high, f_low, f_diff;
|
||||
int uV_high, uV_low, uV;
|
||||
u64 temp, temp_limit;
|
||||
const struct fuse_corner *fuse, *prev_fuse;
|
||||
|
||||
fuse = corner->fuse_corner;
|
||||
prev_fuse = fuse - 1;
|
||||
|
||||
f_high = fuse->max_freq;
|
||||
f_low = prev_fuse->max_freq;
|
||||
uV_high = fuse->uV;
|
||||
uV_low = prev_fuse->uV;
|
||||
f_diff = fuse->max_freq - corner->freq;
|
||||
|
||||
/*
|
||||
* Don't interpolate in the wrong direction. This could happen
|
||||
* if the adjusted fuse voltage overlaps with the previous fuse's
|
||||
* adjusted voltage.
|
||||
*/
|
||||
if (f_high <= f_low || uV_high <= uV_low || f_high <= corner->freq)
|
||||
return corner->uV;
|
||||
|
||||
temp = f_diff * (uV_high - uV_low);
|
||||
temp = div64_ul(temp, f_high - f_low);
|
||||
|
||||
/*
|
||||
* max_volt_scale has units of uV/MHz while freq values
|
||||
* have units of Hz. Divide by 1000000 to convert to.
|
||||
*/
|
||||
temp_limit = f_diff * fdata->max_volt_scale;
|
||||
do_div(temp_limit, 1000000);
|
||||
|
||||
uV = uV_high - min(temp, temp_limit);
|
||||
return roundup(uV, step_volt);
|
||||
}
|
||||
|
||||
int cpr_check_vreg_constraints(struct device *dev, struct regulator *vreg,
|
||||
struct fuse_corner *f)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regulator_is_supported_voltage(vreg, f->min_uV, f->min_uV);
|
||||
if (!ret) {
|
||||
dev_err(dev, "min uV: %d not supported by regulator\n",
|
||||
f->min_uV);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regulator_is_supported_voltage(vreg, f->max_uV, f->max_uV);
|
||||
if (!ret) {
|
||||
dev_err(dev, "max uV: %d not supported by regulator\n",
|
||||
f->max_uV);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
113
drivers/soc/qcom/cpr-common.h
Normal file
113
drivers/soc/qcom/cpr-common.h
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
enum voltage_change_dir {
|
||||
NO_CHANGE,
|
||||
DOWN,
|
||||
UP,
|
||||
};
|
||||
|
||||
struct fuse_corner_data {
|
||||
int ref_uV;
|
||||
int max_uV;
|
||||
int min_uV;
|
||||
int range_uV;
|
||||
/* fuse volt: closed/open loop */
|
||||
int volt_cloop_adjust;
|
||||
int volt_oloop_adjust;
|
||||
int max_volt_scale;
|
||||
int max_quot_scale;
|
||||
/* fuse quot */
|
||||
int quot_offset;
|
||||
int quot_scale;
|
||||
int quot_adjust;
|
||||
/* fuse quot_offset */
|
||||
int quot_offset_scale;
|
||||
int quot_offset_adjust;
|
||||
};
|
||||
|
||||
struct cpr_fuse {
|
||||
char *ring_osc;
|
||||
char *init_voltage;
|
||||
char *quotient;
|
||||
char *quotient_offset;
|
||||
};
|
||||
|
||||
struct fuse_corner {
|
||||
int min_uV;
|
||||
int max_uV;
|
||||
int uV;
|
||||
int quot;
|
||||
int step_quot;
|
||||
const struct reg_sequence *accs;
|
||||
int num_accs;
|
||||
unsigned long max_freq;
|
||||
u8 ring_osc_idx;
|
||||
};
|
||||
|
||||
struct corner {
|
||||
int min_uV;
|
||||
int max_uV;
|
||||
int uV;
|
||||
int last_uV;
|
||||
int quot_adjust;
|
||||
u32 save_ctl;
|
||||
u32 save_irq;
|
||||
unsigned long freq;
|
||||
bool is_open_loop;
|
||||
struct fuse_corner *fuse_corner;
|
||||
};
|
||||
|
||||
struct corner_data {
|
||||
unsigned int fuse_corner;
|
||||
unsigned long freq;
|
||||
};
|
||||
|
||||
struct acc_desc {
|
||||
unsigned int enable_reg;
|
||||
u32 enable_mask;
|
||||
|
||||
struct reg_sequence *config;
|
||||
struct reg_sequence *settings;
|
||||
int num_regs_per_fuse;
|
||||
};
|
||||
|
||||
struct cpr_acc_desc {
|
||||
const struct cpr_desc *cpr_desc;
|
||||
const struct acc_desc *acc_desc;
|
||||
};
|
||||
|
||||
|
||||
int cpr_read_efuse(struct device *dev, const char *cname, u32 *data);
|
||||
int cpr_populate_ring_osc_idx(struct device *dev,
|
||||
struct fuse_corner *fuse_corner,
|
||||
const struct cpr_fuse *cpr_fuse,
|
||||
int num_fuse_corners);
|
||||
int cpr_read_fuse_uV(int init_v_width, int step_size_uV, int ref_uV,
|
||||
int adj, int step_volt, const char *init_v_efuse,
|
||||
struct device *dev);
|
||||
const struct cpr_fuse *cpr_get_fuses(struct device *dev, int tid,
|
||||
int num_fuse_corners);
|
||||
int cpr_populate_fuse_common(struct device *dev,
|
||||
struct fuse_corner_data *fdata,
|
||||
const struct cpr_fuse *cpr_fuse,
|
||||
struct fuse_corner *fuse_corner,
|
||||
int step_volt, int init_v_width,
|
||||
int init_v_step);
|
||||
int cpr_find_initial_corner(struct device *dev, struct clk *cpu_clk,
|
||||
struct corner *corners, int num_corners);
|
||||
u32 cpr_get_fuse_corner(struct dev_pm_opp *opp, u32 tid);
|
||||
unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
|
||||
struct device *cpu_dev);
|
||||
int cpr_calculate_scaling(const char *quot_offset,
|
||||
struct device *dev,
|
||||
const struct fuse_corner_data *fdata,
|
||||
const struct corner *corner);
|
||||
int cpr_interpolate(const struct corner *corner, int step_volt,
|
||||
const struct fuse_corner_data *fdata);
|
||||
int cpr_check_vreg_constraints(struct device *dev, struct regulator *vreg,
|
||||
struct fuse_corner *f);
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include "cpr-common.h"
|
||||
|
||||
/* Register Offsets for RB-CPR and Bit Definitions */
|
||||
|
||||
|
|
@ -124,45 +125,12 @@
|
|||
|
||||
#define FUSE_REVISION_UNKNOWN (-1)
|
||||
|
||||
enum voltage_change_dir {
|
||||
NO_CHANGE,
|
||||
DOWN,
|
||||
UP,
|
||||
};
|
||||
|
||||
struct cpr_fuse {
|
||||
char *ring_osc;
|
||||
char *init_voltage;
|
||||
char *quotient;
|
||||
char *quotient_offset;
|
||||
};
|
||||
|
||||
struct fuse_corner_data {
|
||||
int ref_uV;
|
||||
int max_uV;
|
||||
int min_uV;
|
||||
int max_volt_scale;
|
||||
int max_quot_scale;
|
||||
/* fuse quot */
|
||||
int quot_offset;
|
||||
int quot_scale;
|
||||
int quot_adjust;
|
||||
/* fuse quot_offset */
|
||||
int quot_offset_scale;
|
||||
int quot_offset_adjust;
|
||||
};
|
||||
|
||||
struct cpr_fuses {
|
||||
int init_voltage_step;
|
||||
int init_voltage_width;
|
||||
struct fuse_corner_data *fuse_corner_data;
|
||||
};
|
||||
|
||||
struct corner_data {
|
||||
unsigned int fuse_corner;
|
||||
unsigned long freq;
|
||||
};
|
||||
|
||||
struct cpr_desc {
|
||||
unsigned int num_fuse_corners;
|
||||
int min_diff_quot;
|
||||
|
|
@ -184,44 +152,6 @@ struct cpr_desc {
|
|||
bool reduce_to_corner_uV;
|
||||
};
|
||||
|
||||
struct acc_desc {
|
||||
unsigned int enable_reg;
|
||||
u32 enable_mask;
|
||||
|
||||
struct reg_sequence *config;
|
||||
struct reg_sequence *settings;
|
||||
int num_regs_per_fuse;
|
||||
};
|
||||
|
||||
struct cpr_acc_desc {
|
||||
const struct cpr_desc *cpr_desc;
|
||||
const struct acc_desc *acc_desc;
|
||||
};
|
||||
|
||||
struct fuse_corner {
|
||||
int min_uV;
|
||||
int max_uV;
|
||||
int uV;
|
||||
int quot;
|
||||
int step_quot;
|
||||
const struct reg_sequence *accs;
|
||||
int num_accs;
|
||||
unsigned long max_freq;
|
||||
u8 ring_osc_idx;
|
||||
};
|
||||
|
||||
struct corner {
|
||||
int min_uV;
|
||||
int max_uV;
|
||||
int uV;
|
||||
int last_uV;
|
||||
int quot_adjust;
|
||||
u32 save_ctl;
|
||||
u32 save_irq;
|
||||
unsigned long freq;
|
||||
struct fuse_corner *fuse_corner;
|
||||
};
|
||||
|
||||
struct cpr_drv {
|
||||
unsigned int num_corners;
|
||||
unsigned int ref_clk_khz;
|
||||
|
|
@ -801,62 +731,16 @@ unlock:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
cpr_populate_ring_osc_idx(struct cpr_drv *drv)
|
||||
{
|
||||
struct fuse_corner *fuse = drv->fuse_corners;
|
||||
struct fuse_corner *end = fuse + drv->desc->num_fuse_corners;
|
||||
const struct cpr_fuse *fuses = drv->cpr_fuses;
|
||||
u32 data;
|
||||
int ret;
|
||||
|
||||
for (; fuse < end; fuse++, fuses++) {
|
||||
ret = nvmem_cell_read_variable_le_u32(drv->dev, fuses->ring_osc, &data);
|
||||
if (ret)
|
||||
return ret;
|
||||
fuse->ring_osc_idx = data;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpr_read_fuse_uV(const struct cpr_desc *desc,
|
||||
const struct fuse_corner_data *fdata,
|
||||
const char *init_v_efuse,
|
||||
int step_volt,
|
||||
struct cpr_drv *drv)
|
||||
{
|
||||
int step_size_uV, steps, uV;
|
||||
u32 bits = 0;
|
||||
int ret;
|
||||
|
||||
ret = nvmem_cell_read_variable_le_u32(drv->dev, init_v_efuse, &bits);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
steps = bits & ~BIT(desc->cpr_fuses.init_voltage_width - 1);
|
||||
/* Not two's complement.. instead highest bit is sign bit */
|
||||
if (bits & BIT(desc->cpr_fuses.init_voltage_width - 1))
|
||||
steps = -steps;
|
||||
|
||||
step_size_uV = desc->cpr_fuses.init_voltage_step;
|
||||
|
||||
uV = fdata->ref_uV + steps * step_size_uV;
|
||||
return DIV_ROUND_UP(uV, step_volt) * step_volt;
|
||||
}
|
||||
|
||||
static int cpr_fuse_corner_init(struct cpr_drv *drv)
|
||||
{
|
||||
const struct cpr_desc *desc = drv->desc;
|
||||
const struct cpr_fuse *fuses = drv->cpr_fuses;
|
||||
const struct cpr_fuse *cpr_fuse = drv->cpr_fuses;
|
||||
const struct acc_desc *acc_desc = drv->acc_desc;
|
||||
int i;
|
||||
unsigned int step_volt;
|
||||
struct fuse_corner_data *fdata;
|
||||
struct fuse_corner *fuse, *end;
|
||||
int uV;
|
||||
const struct reg_sequence *accs;
|
||||
int ret;
|
||||
unsigned int step_volt;
|
||||
int i, ret;
|
||||
|
||||
accs = acc_desc->settings;
|
||||
|
||||
|
|
@ -869,24 +753,16 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv)
|
|||
end = &fuse[desc->num_fuse_corners - 1];
|
||||
fdata = desc->cpr_fuses.fuse_corner_data;
|
||||
|
||||
for (i = 0; fuse <= end; fuse++, fuses++, i++, fdata++) {
|
||||
/*
|
||||
* Update SoC voltages: platforms might choose a different
|
||||
* regulators than the one used to characterize the algorithms
|
||||
* (ie, init_voltage_step).
|
||||
*/
|
||||
fdata->min_uV = roundup(fdata->min_uV, step_volt);
|
||||
fdata->max_uV = roundup(fdata->max_uV, step_volt);
|
||||
for (i = 0; fuse <= end; fuse++, cpr_fuse++, i++, fdata++) {
|
||||
ret = cpr_populate_fuse_common(
|
||||
drv->dev, fdata, cpr_fuse,
|
||||
fuse, step_volt,
|
||||
desc->cpr_fuses.init_voltage_width,
|
||||
desc->cpr_fuses.init_voltage_step);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Populate uV */
|
||||
uV = cpr_read_fuse_uV(desc, fdata, fuses->init_voltage,
|
||||
step_volt, drv);
|
||||
if (uV < 0)
|
||||
return uV;
|
||||
|
||||
fuse->min_uV = fdata->min_uV;
|
||||
fuse->max_uV = fdata->max_uV;
|
||||
fuse->uV = clamp(uV, fuse->min_uV, fuse->max_uV);
|
||||
fuse->step_quot = desc->step_quot[fuse->ring_osc_idx];
|
||||
|
||||
if (fuse == end) {
|
||||
/*
|
||||
|
|
@ -898,16 +774,6 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv)
|
|||
end->max_uV = max(end->max_uV, end->uV);
|
||||
}
|
||||
|
||||
/* Populate target quotient by scaling */
|
||||
ret = nvmem_cell_read_variable_le_u32(drv->dev, fuses->quotient, &fuse->quot);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
fuse->quot *= fdata->quot_scale;
|
||||
fuse->quot += fdata->quot_offset;
|
||||
fuse->quot += fdata->quot_adjust;
|
||||
fuse->step_quot = desc->step_quot[fuse->ring_osc_idx];
|
||||
|
||||
/* Populate acc settings */
|
||||
fuse->accs = accs;
|
||||
fuse->num_accs = acc_desc->num_regs_per_fuse;
|
||||
|
|
@ -924,25 +790,9 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv)
|
|||
else if (fuse->uV < fuse->min_uV)
|
||||
fuse->uV = fuse->min_uV;
|
||||
|
||||
ret = regulator_is_supported_voltage(drv->vdd_apc,
|
||||
fuse->min_uV,
|
||||
fuse->min_uV);
|
||||
if (!ret) {
|
||||
dev_err(drv->dev,
|
||||
"min uV: %d (fuse corner: %d) not supported by regulator\n",
|
||||
fuse->min_uV, i);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regulator_is_supported_voltage(drv->vdd_apc,
|
||||
fuse->max_uV,
|
||||
fuse->max_uV);
|
||||
if (!ret) {
|
||||
dev_err(drv->dev,
|
||||
"max uV: %d (fuse corner: %d) not supported by regulator\n",
|
||||
fuse->max_uV, i);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = cpr_check_vreg_constraints(drv->dev, drv->vdd_apc, fuse);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_dbg(drv->dev,
|
||||
"fuse corner %d: [%d %d %d] RO%hhu quot %d squot %d\n",
|
||||
|
|
@ -953,126 +803,6 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int cpr_calculate_scaling(const char *quot_offset,
|
||||
struct cpr_drv *drv,
|
||||
const struct fuse_corner_data *fdata,
|
||||
const struct corner *corner)
|
||||
{
|
||||
u32 quot_diff = 0;
|
||||
unsigned long freq_diff;
|
||||
int scaling;
|
||||
const struct fuse_corner *fuse, *prev_fuse;
|
||||
int ret;
|
||||
|
||||
fuse = corner->fuse_corner;
|
||||
prev_fuse = fuse - 1;
|
||||
|
||||
if (quot_offset) {
|
||||
ret = nvmem_cell_read_variable_le_u32(drv->dev, quot_offset, "_diff);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
quot_diff *= fdata->quot_offset_scale;
|
||||
quot_diff += fdata->quot_offset_adjust;
|
||||
} else {
|
||||
quot_diff = fuse->quot - prev_fuse->quot;
|
||||
}
|
||||
|
||||
freq_diff = fuse->max_freq - prev_fuse->max_freq;
|
||||
freq_diff /= 1000000; /* Convert to MHz */
|
||||
scaling = 1000 * quot_diff / freq_diff;
|
||||
return min(scaling, fdata->max_quot_scale);
|
||||
}
|
||||
|
||||
static int cpr_interpolate(const struct corner *corner, int step_volt,
|
||||
const struct fuse_corner_data *fdata)
|
||||
{
|
||||
unsigned long f_high, f_low, f_diff;
|
||||
int uV_high, uV_low, uV;
|
||||
u64 temp, temp_limit;
|
||||
const struct fuse_corner *fuse, *prev_fuse;
|
||||
|
||||
fuse = corner->fuse_corner;
|
||||
prev_fuse = fuse - 1;
|
||||
|
||||
f_high = fuse->max_freq;
|
||||
f_low = prev_fuse->max_freq;
|
||||
uV_high = fuse->uV;
|
||||
uV_low = prev_fuse->uV;
|
||||
f_diff = fuse->max_freq - corner->freq;
|
||||
|
||||
/*
|
||||
* Don't interpolate in the wrong direction. This could happen
|
||||
* if the adjusted fuse voltage overlaps with the previous fuse's
|
||||
* adjusted voltage.
|
||||
*/
|
||||
if (f_high <= f_low || uV_high <= uV_low || f_high <= corner->freq)
|
||||
return corner->uV;
|
||||
|
||||
temp = f_diff * (uV_high - uV_low);
|
||||
temp = div64_ul(temp, f_high - f_low);
|
||||
|
||||
/*
|
||||
* max_volt_scale has units of uV/MHz while freq values
|
||||
* have units of Hz. Divide by 1000000 to convert to.
|
||||
*/
|
||||
temp_limit = f_diff * fdata->max_volt_scale;
|
||||
do_div(temp_limit, 1000000);
|
||||
|
||||
uV = uV_high - min(temp, temp_limit);
|
||||
return roundup(uV, step_volt);
|
||||
}
|
||||
|
||||
static unsigned int cpr_get_fuse_corner(struct dev_pm_opp *opp)
|
||||
{
|
||||
struct device_node *np;
|
||||
unsigned int fuse_corner = 0;
|
||||
|
||||
np = dev_pm_opp_get_of_node(opp);
|
||||
if (of_property_read_u32(np, "qcom,opp-fuse-level", &fuse_corner))
|
||||
pr_err("%s: missing 'qcom,opp-fuse-level' property\n",
|
||||
__func__);
|
||||
|
||||
of_node_put(np);
|
||||
|
||||
return fuse_corner;
|
||||
}
|
||||
|
||||
static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
|
||||
struct device *cpu_dev)
|
||||
{
|
||||
u64 rate = 0;
|
||||
struct device_node *ref_np;
|
||||
struct device_node *desc_np;
|
||||
struct device_node *child_np = NULL;
|
||||
struct device_node *child_req_np = NULL;
|
||||
|
||||
desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
|
||||
if (!desc_np)
|
||||
return 0;
|
||||
|
||||
ref_np = dev_pm_opp_get_of_node(ref);
|
||||
if (!ref_np)
|
||||
goto out_ref;
|
||||
|
||||
do {
|
||||
of_node_put(child_req_np);
|
||||
child_np = of_get_next_available_child(desc_np, child_np);
|
||||
child_req_np = of_parse_phandle(child_np, "required-opps", 0);
|
||||
} while (child_np && child_req_np != ref_np);
|
||||
|
||||
if (child_np && child_req_np == ref_np)
|
||||
of_property_read_u64(child_np, "opp-hz", &rate);
|
||||
|
||||
of_node_put(child_req_np);
|
||||
of_node_put(child_np);
|
||||
of_node_put(ref_np);
|
||||
out_ref:
|
||||
of_node_put(desc_np);
|
||||
|
||||
return (unsigned long) rate;
|
||||
}
|
||||
|
||||
static int cpr_corner_init(struct cpr_drv *drv)
|
||||
{
|
||||
const struct cpr_desc *desc = drv->desc;
|
||||
|
|
@ -1110,7 +840,7 @@ static int cpr_corner_init(struct cpr_drv *drv)
|
|||
opp = dev_pm_opp_find_level_exact(&drv->pd.dev, level);
|
||||
if (IS_ERR(opp))
|
||||
return -EINVAL;
|
||||
fc = cpr_get_fuse_corner(opp);
|
||||
fc = cpr_get_fuse_corner(opp, 0);
|
||||
if (!fc) {
|
||||
dev_pm_opp_put(opp);
|
||||
return -EINVAL;
|
||||
|
|
@ -1186,7 +916,7 @@ static int cpr_corner_init(struct cpr_drv *drv)
|
|||
corner->uV = fuse->uV;
|
||||
|
||||
if (prev_fuse && cdata[i - 1].freq == prev_fuse->max_freq) {
|
||||
scaling = cpr_calculate_scaling(quot_offset, drv,
|
||||
scaling = cpr_calculate_scaling(quot_offset, drv->dev,
|
||||
fdata, corner);
|
||||
if (scaling < 0)
|
||||
return scaling;
|
||||
|
|
@ -1224,47 +954,6 @@ static int cpr_corner_init(struct cpr_drv *drv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct cpr_fuse *cpr_get_fuses(struct cpr_drv *drv)
|
||||
{
|
||||
const struct cpr_desc *desc = drv->desc;
|
||||
struct cpr_fuse *fuses;
|
||||
int i;
|
||||
|
||||
fuses = devm_kcalloc(drv->dev, desc->num_fuse_corners,
|
||||
sizeof(struct cpr_fuse),
|
||||
GFP_KERNEL);
|
||||
if (!fuses)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (i = 0; i < desc->num_fuse_corners; i++) {
|
||||
char tbuf[32];
|
||||
|
||||
snprintf(tbuf, 32, "cpr_ring_osc%d", i + 1);
|
||||
fuses[i].ring_osc = devm_kstrdup(drv->dev, tbuf, GFP_KERNEL);
|
||||
if (!fuses[i].ring_osc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
snprintf(tbuf, 32, "cpr_init_voltage%d", i + 1);
|
||||
fuses[i].init_voltage = devm_kstrdup(drv->dev, tbuf,
|
||||
GFP_KERNEL);
|
||||
if (!fuses[i].init_voltage)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
snprintf(tbuf, 32, "cpr_quotient%d", i + 1);
|
||||
fuses[i].quotient = devm_kstrdup(drv->dev, tbuf, GFP_KERNEL);
|
||||
if (!fuses[i].quotient)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
snprintf(tbuf, 32, "cpr_quotient_offset%d", i + 1);
|
||||
fuses[i].quotient_offset = devm_kstrdup(drv->dev, tbuf,
|
||||
GFP_KERNEL);
|
||||
if (!fuses[i].quotient_offset)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
return fuses;
|
||||
}
|
||||
|
||||
static void cpr_set_loop_allowed(struct cpr_drv *drv)
|
||||
{
|
||||
drv->loop_disabled = false;
|
||||
|
|
@ -1296,54 +985,6 @@ static int cpr_init_parameters(struct cpr_drv *drv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int cpr_find_initial_corner(struct cpr_drv *drv)
|
||||
{
|
||||
unsigned long rate;
|
||||
const struct corner *end;
|
||||
struct corner *iter;
|
||||
unsigned int i = 0;
|
||||
|
||||
if (!drv->cpu_clk) {
|
||||
dev_err(drv->dev, "cannot get rate from NULL clk\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
end = &drv->corners[drv->num_corners - 1];
|
||||
rate = clk_get_rate(drv->cpu_clk);
|
||||
|
||||
/*
|
||||
* Some bootloaders set a CPU clock frequency that is not defined
|
||||
* in the OPP table. When running at an unlisted frequency,
|
||||
* cpufreq_online() will change to the OPP which has the lowest
|
||||
* frequency, at or above the unlisted frequency.
|
||||
* Since cpufreq_online() always "rounds up" in the case of an
|
||||
* unlisted frequency, this function always "rounds down" in case
|
||||
* of an unlisted frequency. That way, when cpufreq_online()
|
||||
* triggers the first ever call to cpr_set_performance_state(),
|
||||
* it will correctly determine the direction as UP.
|
||||
*/
|
||||
for (iter = drv->corners; iter <= end; iter++) {
|
||||
if (iter->freq > rate)
|
||||
break;
|
||||
i++;
|
||||
if (iter->freq == rate) {
|
||||
drv->corner = iter;
|
||||
break;
|
||||
}
|
||||
if (iter->freq < rate)
|
||||
drv->corner = iter;
|
||||
}
|
||||
|
||||
if (!drv->corner) {
|
||||
dev_err(drv->dev, "boot up corner not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(drv->dev, "boot up perf state: %u\n", i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct cpr_desc qcs404_cpr_desc = {
|
||||
.num_fuse_corners = 3,
|
||||
.min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF,
|
||||
|
|
@ -1531,8 +1172,9 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain,
|
|||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
ret = cpr_find_initial_corner(drv);
|
||||
if (ret)
|
||||
ret = cpr_find_initial_corner(drv->dev, drv->cpu_clk, drv->corners,
|
||||
drv->num_corners);
|
||||
if (ret < 0)
|
||||
goto unlock;
|
||||
|
||||
if (acc_desc->config)
|
||||
|
|
@ -1616,6 +1258,7 @@ static int cpr_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cpr_drv *drv;
|
||||
const struct cpr_desc *desc;
|
||||
int irq, ret;
|
||||
const struct cpr_acc_desc *data;
|
||||
struct device_node *np;
|
||||
|
|
@ -1631,6 +1274,7 @@ static int cpr_probe(struct platform_device *pdev)
|
|||
drv->dev = dev;
|
||||
drv->desc = data->cpr_desc;
|
||||
drv->acc_desc = data->acc_desc;
|
||||
desc = drv->desc;
|
||||
|
||||
drv->fuse_corners = devm_kcalloc(dev, drv->desc->num_fuse_corners,
|
||||
sizeof(*drv->fuse_corners),
|
||||
|
|
@ -1670,11 +1314,13 @@ static int cpr_probe(struct platform_device *pdev)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
drv->cpr_fuses = cpr_get_fuses(drv);
|
||||
drv->cpr_fuses = cpr_get_fuses(drv->dev, 0, desc->num_fuse_corners);
|
||||
if (IS_ERR(drv->cpr_fuses))
|
||||
return PTR_ERR(drv->cpr_fuses);
|
||||
|
||||
ret = cpr_populate_ring_osc_idx(drv);
|
||||
ret = cpr_populate_ring_osc_idx(drv->dev, drv->fuse_corners,
|
||||
drv->cpr_fuses,
|
||||
desc->num_fuse_corners);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue