2602 lines
64 KiB
C
2602 lines
64 KiB
C
/*
|
|
* Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
//#define DEBUG
|
|
#include <linux/clk.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/devfreq.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/nvmem-consumer.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/rockchip/rockchip_sip.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/soc/rockchip/pvtm.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/pm_opp.h>
|
|
#include <linux/version.h>
|
|
#include <soc/rockchip/rockchip_opp_select.h>
|
|
|
|
#include "../../clk/rockchip/clk.h"
|
|
#include "../../opp/opp.h"
|
|
#include "../../devfreq/governor.h"
|
|
|
|
#define MAX_PROP_NAME_LEN 6
|
|
#define SEL_TABLE_END ~1
|
|
#define AVS_DELETE_OPP 0
|
|
#define AVS_SCALING_RATE 1
|
|
|
|
#define LEAKAGE_V1 1
|
|
#define LEAKAGE_V2 2
|
|
#define LEAKAGE_V3 3
|
|
|
|
#define to_thermal_opp_info(nb) container_of(nb, struct thermal_opp_info, \
|
|
thermal_nb)
|
|
|
|
struct sel_table {
|
|
int min;
|
|
int max;
|
|
int sel;
|
|
};
|
|
|
|
struct bin_sel_table {
|
|
int bin;
|
|
int sel;
|
|
};
|
|
|
|
struct pvtm_config {
|
|
unsigned int freq;
|
|
unsigned int volt;
|
|
unsigned int ch[2];
|
|
unsigned int sample_time;
|
|
unsigned int num;
|
|
unsigned int err;
|
|
unsigned int ref_temp;
|
|
unsigned int offset;
|
|
int temp_prop[2];
|
|
const char *tz_name;
|
|
struct thermal_zone_device *tz;
|
|
};
|
|
|
|
struct lkg_conversion_table {
|
|
int temp;
|
|
int conv;
|
|
};
|
|
|
|
struct otp_opp_info {
|
|
u16 min_freq;
|
|
u16 max_freq;
|
|
u8 volt;
|
|
u8 length;
|
|
} __packed;
|
|
|
|
#define PVTM_CH_MAX 8
|
|
#define PVTM_SUB_CH_MAX 8
|
|
|
|
#define FRAC_BITS 10
|
|
#define int_to_frac(x) ((x) << FRAC_BITS)
|
|
#define frac_to_int(x) ((x) >> FRAC_BITS)
|
|
|
|
static int pvtm_value[PVTM_CH_MAX][PVTM_SUB_CH_MAX];
|
|
static int lkg_version;
|
|
|
|
static int rockchip_init_read_margin(struct device *dev,
|
|
struct rockchip_opp_info *opp_info,
|
|
const char *reg_name);
|
|
|
|
/*
|
|
* temp = temp * 10
|
|
* conv = exp(-ln(1.2) / 5 * (temp - 23)) * 100
|
|
*/
|
|
static const struct lkg_conversion_table conv_table[] = {
|
|
{ 200, 111 },
|
|
{ 205, 109 },
|
|
{ 210, 107 },
|
|
{ 215, 105 },
|
|
{ 220, 103 },
|
|
{ 225, 101 },
|
|
{ 230, 100 },
|
|
{ 235, 98 },
|
|
{ 240, 96 },
|
|
{ 245, 94 },
|
|
{ 250, 92 },
|
|
{ 255, 91 },
|
|
{ 260, 89 },
|
|
{ 265, 88 },
|
|
{ 270, 86 },
|
|
{ 275, 84 },
|
|
{ 280, 83 },
|
|
{ 285, 81 },
|
|
{ 290, 80 },
|
|
{ 295, 78 },
|
|
{ 300, 77 },
|
|
{ 305, 76 },
|
|
{ 310, 74 },
|
|
{ 315, 73 },
|
|
{ 320, 72 },
|
|
{ 325, 70 },
|
|
{ 330, 69 },
|
|
{ 335, 68 },
|
|
{ 340, 66 },
|
|
{ 345, 65 },
|
|
{ 350, 64 },
|
|
{ 355, 63 },
|
|
{ 360, 62 },
|
|
{ 365, 61 },
|
|
{ 370, 60 },
|
|
{ 375, 58 },
|
|
{ 380, 57 },
|
|
{ 385, 56 },
|
|
{ 390, 55 },
|
|
{ 395, 54 },
|
|
{ 400, 53 },
|
|
};
|
|
|
|
static int rockchip_nvmem_cell_read_common(struct device_node *np,
|
|
const char *cell_id,
|
|
void *val, size_t count)
|
|
{
|
|
struct nvmem_cell *cell;
|
|
void *buf;
|
|
size_t len;
|
|
|
|
cell = of_nvmem_cell_get(np, cell_id);
|
|
if (IS_ERR(cell))
|
|
return PTR_ERR(cell);
|
|
|
|
buf = nvmem_cell_read(cell, &len);
|
|
if (IS_ERR(buf)) {
|
|
nvmem_cell_put(cell);
|
|
return PTR_ERR(buf);
|
|
}
|
|
if (len != count) {
|
|
kfree(buf);
|
|
nvmem_cell_put(cell);
|
|
return -EINVAL;
|
|
}
|
|
memcpy(val, buf, count);
|
|
kfree(buf);
|
|
nvmem_cell_put(cell);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rockchip_nvmem_cell_read_u8(struct device_node *np, const char *cell_id,
|
|
u8 *val)
|
|
{
|
|
return rockchip_nvmem_cell_read_common(np, cell_id, val, sizeof(*val));
|
|
}
|
|
EXPORT_SYMBOL(rockchip_nvmem_cell_read_u8);
|
|
|
|
int rockchip_nvmem_cell_read_u16(struct device_node *np, const char *cell_id,
|
|
u16 *val)
|
|
{
|
|
return rockchip_nvmem_cell_read_common(np, cell_id, val, sizeof(*val));
|
|
}
|
|
EXPORT_SYMBOL(rockchip_nvmem_cell_read_u16);
|
|
|
|
static int rockchip_get_sel_table(struct device_node *np, char *porp_name,
|
|
struct sel_table **table)
|
|
{
|
|
struct sel_table *sel_table;
|
|
const struct property *prop;
|
|
int count, i;
|
|
|
|
prop = of_find_property(np, porp_name, NULL);
|
|
if (!prop)
|
|
return -EINVAL;
|
|
|
|
if (!prop->value)
|
|
return -ENODATA;
|
|
|
|
count = of_property_count_u32_elems(np, porp_name);
|
|
if (count < 0)
|
|
return -EINVAL;
|
|
|
|
if (count % 3)
|
|
return -EINVAL;
|
|
|
|
sel_table = kzalloc(sizeof(*sel_table) * (count / 3 + 1), GFP_KERNEL);
|
|
if (!sel_table)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < count / 3; i++) {
|
|
of_property_read_u32_index(np, porp_name, 3 * i,
|
|
&sel_table[i].min);
|
|
of_property_read_u32_index(np, porp_name, 3 * i + 1,
|
|
&sel_table[i].max);
|
|
of_property_read_u32_index(np, porp_name, 3 * i + 2,
|
|
&sel_table[i].sel);
|
|
}
|
|
sel_table[i].min = 0;
|
|
sel_table[i].max = 0;
|
|
sel_table[i].sel = SEL_TABLE_END;
|
|
|
|
*table = sel_table;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_get_bin_sel_table(struct device_node *np, char *porp_name,
|
|
struct bin_sel_table **table)
|
|
{
|
|
struct bin_sel_table *sel_table;
|
|
const struct property *prop;
|
|
int count, i;
|
|
|
|
prop = of_find_property(np, porp_name, NULL);
|
|
if (!prop)
|
|
return -EINVAL;
|
|
|
|
if (!prop->value)
|
|
return -ENODATA;
|
|
|
|
count = of_property_count_u32_elems(np, porp_name);
|
|
if (count < 0)
|
|
return -EINVAL;
|
|
|
|
if (count % 2)
|
|
return -EINVAL;
|
|
|
|
sel_table = kzalloc(sizeof(*sel_table) * (count / 2 + 1), GFP_KERNEL);
|
|
if (!sel_table)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < count / 2; i++) {
|
|
of_property_read_u32_index(np, porp_name, 2 * i,
|
|
&sel_table[i].bin);
|
|
of_property_read_u32_index(np, porp_name, 2 * i + 1,
|
|
&sel_table[i].sel);
|
|
}
|
|
|
|
sel_table[i].bin = 0;
|
|
sel_table[i].sel = SEL_TABLE_END;
|
|
|
|
*table = sel_table;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_get_sel(struct device_node *np, char *name,
|
|
int value, int *sel)
|
|
{
|
|
struct sel_table *table = NULL;
|
|
int i, ret = -EINVAL;
|
|
|
|
if (!sel)
|
|
return -EINVAL;
|
|
|
|
if (rockchip_get_sel_table(np, name, &table))
|
|
return -EINVAL;
|
|
|
|
for (i = 0; table[i].sel != SEL_TABLE_END; i++) {
|
|
if (value >= table[i].min) {
|
|
*sel = table[i].sel;
|
|
ret = 0;
|
|
}
|
|
}
|
|
kfree(table);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_get_bin_sel(struct device_node *np, char *name,
|
|
int value, int *sel)
|
|
{
|
|
struct bin_sel_table *table = NULL;
|
|
int i, ret = -EINVAL;
|
|
|
|
if (!sel)
|
|
return -EINVAL;
|
|
|
|
if (rockchip_get_bin_sel_table(np, name, &table))
|
|
return -EINVAL;
|
|
|
|
for (i = 0; table[i].sel != SEL_TABLE_END; i++) {
|
|
if (value == table[i].bin) {
|
|
*sel = table[i].sel;
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
kfree(table);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_parse_pvtm_config(struct device_node *np,
|
|
struct pvtm_config *pvtm)
|
|
{
|
|
if (of_property_read_u32(np, "rockchip,pvtm-freq", &pvtm->freq))
|
|
return -EINVAL;
|
|
if (of_property_read_u32(np, "rockchip,pvtm-volt", &pvtm->volt))
|
|
return -EINVAL;
|
|
if (of_property_read_u32(np, "rockchip,pvtm-sample-time",
|
|
&pvtm->sample_time))
|
|
return -EINVAL;
|
|
if (of_property_read_u32(np, "rockchip,pvtm-ref-temp", &pvtm->ref_temp))
|
|
return -EINVAL;
|
|
if (of_property_read_u32_array(np, "rockchip,pvtm-temp-prop",
|
|
pvtm->temp_prop, 2))
|
|
return -EINVAL;
|
|
if (of_property_read_string(np, "rockchip,pvtm-thermal-zone",
|
|
&pvtm->tz_name)) {
|
|
if (of_property_read_string(np, "rockchip,thermal-zone",
|
|
&pvtm->tz_name))
|
|
return -EINVAL;
|
|
}
|
|
pvtm->tz = thermal_zone_get_zone_by_name(pvtm->tz_name);
|
|
if (IS_ERR(pvtm->tz))
|
|
return -EINVAL;
|
|
if (!pvtm->tz->ops->get_temp)
|
|
return -EINVAL;
|
|
if (of_property_read_bool(np, "rockchip,pvtm-pvtpll")) {
|
|
if (of_property_read_u32(np, "rockchip,pvtm-offset",
|
|
&pvtm->offset))
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
if (of_property_read_u32_array(np, "rockchip,pvtm-ch", pvtm->ch, 2))
|
|
return -EINVAL;
|
|
if (pvtm->ch[0] >= PVTM_CH_MAX || pvtm->ch[1] >= PVTM_SUB_CH_MAX)
|
|
return -EINVAL;
|
|
if (of_property_read_u32(np, "rockchip,pvtm-number", &pvtm->num))
|
|
return -EINVAL;
|
|
if (of_property_read_u32(np, "rockchip,pvtm-error", &pvtm->err))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_get_pvtm_specific_value(struct device *dev,
|
|
struct device_node *np,
|
|
struct clk *clk,
|
|
struct regulator *reg,
|
|
int *target_value)
|
|
{
|
|
struct pvtm_config *pvtm;
|
|
unsigned long old_freq;
|
|
unsigned int old_volt;
|
|
int cur_temp, diff_temp;
|
|
int cur_value, total_value, avg_value, diff_value;
|
|
int min_value, max_value;
|
|
int ret = 0, i = 0, retry = 2;
|
|
|
|
pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL);
|
|
if (!pvtm)
|
|
return -ENOMEM;
|
|
|
|
ret = rockchip_parse_pvtm_config(np, pvtm);
|
|
if (ret)
|
|
goto pvtm_value_out;
|
|
|
|
old_freq = clk_get_rate(clk);
|
|
old_volt = regulator_get_voltage(reg);
|
|
|
|
/*
|
|
* Set pvtm_freq to the lowest frequency in dts,
|
|
* so change frequency first.
|
|
*/
|
|
ret = clk_set_rate(clk, pvtm->freq * 1000);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to set pvtm freq\n");
|
|
goto pvtm_value_out;
|
|
}
|
|
|
|
ret = regulator_set_voltage(reg, pvtm->volt, pvtm->volt);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to set pvtm_volt\n");
|
|
goto restore_clk;
|
|
}
|
|
|
|
/* The first few values may be fluctuant, if error is too big, retry*/
|
|
while (retry--) {
|
|
total_value = 0;
|
|
min_value = INT_MAX;
|
|
max_value = 0;
|
|
|
|
for (i = 0; i < pvtm->num; i++) {
|
|
cur_value = rockchip_get_pvtm_value(pvtm->ch[0],
|
|
pvtm->ch[1],
|
|
pvtm->sample_time);
|
|
if (cur_value <= 0) {
|
|
ret = -EINVAL;
|
|
goto resetore_volt;
|
|
}
|
|
if (cur_value < min_value)
|
|
min_value = cur_value;
|
|
if (cur_value > max_value)
|
|
max_value = cur_value;
|
|
total_value += cur_value;
|
|
}
|
|
if (max_value - min_value < pvtm->err)
|
|
break;
|
|
}
|
|
if (!total_value || !pvtm->num) {
|
|
ret = -EINVAL;
|
|
goto resetore_volt;
|
|
}
|
|
avg_value = total_value / pvtm->num;
|
|
|
|
/*
|
|
* As pvtm is influenced by temperature, compute difference between
|
|
* current temperature and reference temperature
|
|
*/
|
|
pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp);
|
|
diff_temp = (cur_temp / 1000 - pvtm->ref_temp);
|
|
diff_value = diff_temp *
|
|
(diff_temp < 0 ? pvtm->temp_prop[0] : pvtm->temp_prop[1]);
|
|
*target_value = avg_value + diff_value;
|
|
|
|
pvtm_value[pvtm->ch[0]][pvtm->ch[1]] = *target_value;
|
|
|
|
dev_info(dev, "temp=%d, pvtm=%d (%d + %d)\n",
|
|
cur_temp, *target_value, avg_value, diff_value);
|
|
|
|
resetore_volt:
|
|
regulator_set_voltage(reg, old_volt, INT_MAX);
|
|
restore_clk:
|
|
clk_set_rate(clk, old_freq);
|
|
pvtm_value_out:
|
|
kfree(pvtm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* mul_frac() - multiply two fixed-point numbers
|
|
* @x: first multiplicand
|
|
* @y: second multiplicand
|
|
*
|
|
* Return: the result of multiplying two fixed-point numbers. The
|
|
* result is also a fixed-point number.
|
|
*/
|
|
static inline s64 mul_frac(s64 x, s64 y)
|
|
{
|
|
return (x * y) >> FRAC_BITS;
|
|
}
|
|
|
|
static int temp_to_conversion_rate(int temp)
|
|
{
|
|
int high, low, mid;
|
|
|
|
low = 0;
|
|
high = ARRAY_SIZE(conv_table) - 1;
|
|
mid = (high + low) / 2;
|
|
|
|
/* No temp available, return max conversion_rate */
|
|
if (temp <= conv_table[low].temp)
|
|
return conv_table[low].conv;
|
|
if (temp >= conv_table[high].temp)
|
|
return conv_table[high].conv;
|
|
|
|
while (low <= high) {
|
|
if (temp <= conv_table[mid].temp && temp >
|
|
conv_table[mid - 1].temp) {
|
|
return conv_table[mid - 1].conv +
|
|
(conv_table[mid].conv - conv_table[mid - 1].conv) *
|
|
(temp - conv_table[mid - 1].temp) /
|
|
(conv_table[mid].temp - conv_table[mid - 1].temp);
|
|
} else if (temp > conv_table[mid].temp) {
|
|
low = mid + 1;
|
|
} else {
|
|
high = mid - 1;
|
|
}
|
|
mid = (low + high) / 2;
|
|
}
|
|
|
|
return 100;
|
|
}
|
|
|
|
static int rockchip_adjust_leakage(struct device *dev, struct device_node *np,
|
|
int *leakage)
|
|
{
|
|
struct nvmem_cell *cell;
|
|
u8 value = 0;
|
|
u32 temp;
|
|
int conversion;
|
|
int ret;
|
|
|
|
cell = of_nvmem_cell_get(np, "leakage_temp");
|
|
if (IS_ERR(cell))
|
|
goto next;
|
|
nvmem_cell_put(cell);
|
|
ret = rockchip_nvmem_cell_read_u8(np, "leakage_temp", &value);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to get leakage temp\n");
|
|
return -EINVAL;
|
|
}
|
|
/*
|
|
* The ambient temperature range: 20C to 40C
|
|
* In order to improve the precision, we do a conversion.
|
|
* The temp in efuse : temp_efuse = (temp - 20) / (40 - 20) * 63
|
|
* The ambient temp : temp = (temp_efuse / 63) * (40 - 20) + 20
|
|
* Reserves a decimal point : temp = temp * 10
|
|
*/
|
|
temp = value;
|
|
temp = mul_frac((int_to_frac(temp) / 63 * 20 + int_to_frac(20)),
|
|
int_to_frac(10));
|
|
conversion = temp_to_conversion_rate(frac_to_int(temp));
|
|
*leakage = *leakage * conversion / 100;
|
|
|
|
next:
|
|
cell = of_nvmem_cell_get(np, "leakage_volt");
|
|
if (IS_ERR(cell))
|
|
return 0;
|
|
nvmem_cell_put(cell);
|
|
ret = rockchip_nvmem_cell_read_u8(np, "leakage_volt", &value);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to get leakage volt\n");
|
|
return -EINVAL;
|
|
}
|
|
/*
|
|
* if ft write leakage use 1.35v, need convert to 1v.
|
|
* leakage(1v) = leakage(1.35v) / 4
|
|
*/
|
|
if (value)
|
|
*leakage = *leakage / 4;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_get_leakage_version(int *version)
|
|
{
|
|
if (*version)
|
|
return 0;
|
|
|
|
if (of_machine_is_compatible("rockchip,rk3368"))
|
|
*version = LEAKAGE_V2;
|
|
else if (of_machine_is_compatible("rockchip,rv1126") ||
|
|
of_machine_is_compatible("rockchip,rv1109"))
|
|
*version = LEAKAGE_V3;
|
|
else
|
|
*version = LEAKAGE_V1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_get_leakage_v1(struct device *dev, struct device_node *np,
|
|
char *lkg_name, int *leakage)
|
|
{
|
|
struct nvmem_cell *cell;
|
|
int ret = 0;
|
|
u8 value = 0;
|
|
|
|
cell = of_nvmem_cell_get(np, "leakage");
|
|
if (IS_ERR(cell)) {
|
|
ret = rockchip_nvmem_cell_read_u8(np, lkg_name, &value);
|
|
} else {
|
|
nvmem_cell_put(cell);
|
|
ret = rockchip_nvmem_cell_read_u8(np, "leakage", &value);
|
|
}
|
|
if (ret)
|
|
dev_err(dev, "Failed to get %s\n", lkg_name);
|
|
else
|
|
*leakage = value;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_get_leakage_v2(struct device *dev, struct device_node *np,
|
|
char *lkg_name, int *leakage)
|
|
{
|
|
int lkg = 0, ret = 0;
|
|
|
|
if (rockchip_get_leakage_v1(dev, np, lkg_name, &lkg))
|
|
return -EINVAL;
|
|
|
|
ret = rockchip_adjust_leakage(dev, np, &lkg);
|
|
if (ret)
|
|
dev_err(dev, "Failed to adjust leakage, value=%d\n", lkg);
|
|
else
|
|
*leakage = lkg;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_get_leakage_v3(struct device *dev, struct device_node *np,
|
|
char *lkg_name, int *leakage)
|
|
{
|
|
int lkg = 0;
|
|
|
|
if (rockchip_get_leakage_v1(dev, np, lkg_name, &lkg))
|
|
return -EINVAL;
|
|
|
|
*leakage = (((lkg & 0xf8) >> 3) * 1000) + ((lkg & 0x7) * 125);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rockchip_of_get_leakage(struct device *dev, char *lkg_name, int *leakage)
|
|
{
|
|
struct device_node *np;
|
|
int ret = -EINVAL;
|
|
|
|
np = of_parse_phandle(dev->of_node, "operating-points-v2", 0);
|
|
if (!np) {
|
|
dev_warn(dev, "OPP-v2 not supported\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
rockchip_get_leakage_version(&lkg_version);
|
|
|
|
switch (lkg_version) {
|
|
case LEAKAGE_V1:
|
|
ret = rockchip_get_leakage_v1(dev, np, lkg_name, leakage);
|
|
break;
|
|
case LEAKAGE_V2:
|
|
ret = rockchip_get_leakage_v2(dev, np, lkg_name, leakage);
|
|
break;
|
|
case LEAKAGE_V3:
|
|
ret = rockchip_get_leakage_v3(dev, np, lkg_name, leakage);
|
|
if (!ret) {
|
|
/*
|
|
* round up to the nearest whole number for calculating
|
|
* static power, it does not need to be precise.
|
|
*/
|
|
if (*leakage % 1000 > 500)
|
|
*leakage = *leakage / 1000 + 1;
|
|
else
|
|
*leakage = *leakage / 1000;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
of_node_put(np);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_of_get_leakage);
|
|
|
|
static void rockchip_of_get_lkg_sel(struct device *dev, struct device_node *np,
|
|
char *lkg_name, int process,
|
|
int *volt_sel, int *scale_sel)
|
|
{
|
|
struct property *prop = NULL;
|
|
int leakage = -EINVAL, ret = 0;
|
|
char name[NAME_MAX];
|
|
|
|
rockchip_get_leakage_version(&lkg_version);
|
|
|
|
switch (lkg_version) {
|
|
case LEAKAGE_V1:
|
|
ret = rockchip_get_leakage_v1(dev, np, lkg_name, &leakage);
|
|
if (ret)
|
|
return;
|
|
dev_info(dev, "leakage=%d\n", leakage);
|
|
break;
|
|
case LEAKAGE_V2:
|
|
ret = rockchip_get_leakage_v2(dev, np, lkg_name, &leakage);
|
|
if (ret)
|
|
return;
|
|
dev_info(dev, "leakage=%d\n", leakage);
|
|
break;
|
|
case LEAKAGE_V3:
|
|
ret = rockchip_get_leakage_v3(dev, np, lkg_name, &leakage);
|
|
if (ret)
|
|
return;
|
|
dev_info(dev, "leakage=%d.%d\n", leakage / 1000,
|
|
leakage % 1000);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (!volt_sel)
|
|
goto next;
|
|
if (process >= 0) {
|
|
snprintf(name, sizeof(name),
|
|
"rockchip,p%d-leakage-voltage-sel", process);
|
|
prop = of_find_property(np, name, NULL);
|
|
}
|
|
if (!prop)
|
|
sprintf(name, "rockchip,leakage-voltage-sel");
|
|
ret = rockchip_get_sel(np, name, leakage, volt_sel);
|
|
if (!ret)
|
|
dev_info(dev, "leakage-volt-sel=%d\n", *volt_sel);
|
|
|
|
next:
|
|
if (!scale_sel)
|
|
return;
|
|
if (process >= 0) {
|
|
snprintf(name, sizeof(name),
|
|
"rockchip,p%d-leakage-scaling-sel", process);
|
|
prop = of_find_property(np, name, NULL);
|
|
}
|
|
if (!prop)
|
|
sprintf(name, "rockchip,leakage-scaling-sel");
|
|
ret = rockchip_get_sel(np, name, leakage, scale_sel);
|
|
if (!ret)
|
|
dev_info(dev, "leakage-scale=%d\n", *scale_sel);
|
|
}
|
|
|
|
static unsigned long rockchip_pvtpll_get_rate(struct rockchip_opp_info *info)
|
|
{
|
|
unsigned int rate0, rate1, delta;
|
|
int i;
|
|
|
|
#define MIN_STABLE_DELTA 3
|
|
regmap_read(info->pvtpll_base, info->pvtpll_avg_offset, &rate0);
|
|
/* max delay 2ms */
|
|
for (i = 0; i < 20; i++) {
|
|
udelay(100);
|
|
regmap_read(info->pvtpll_base, info->pvtpll_avg_offset, &rate1);
|
|
delta = abs(rate1 - rate0);
|
|
rate0 = rate1;
|
|
if (delta <= MIN_STABLE_DELTA)
|
|
break;
|
|
}
|
|
|
|
if (delta > MIN_STABLE_DELTA) {
|
|
dev_err(info->dev, "%s: bad delta: %u\n", __func__, delta);
|
|
return 0;
|
|
}
|
|
|
|
return rate0 * 1000000;
|
|
}
|
|
|
|
static int rockchip_pvtpll_parse_dt(struct rockchip_opp_info *info)
|
|
{
|
|
struct device_node *np;
|
|
int ret;
|
|
|
|
np = of_parse_phandle(info->dev->of_node, "operating-points-v2", 0);
|
|
if (!np) {
|
|
dev_warn(info->dev, "OPP-v2 not supported\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "rockchip,pvtpll-avg-offset", &info->pvtpll_avg_offset);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = of_property_read_u32(np, "rockchip,pvtpll-min-rate", &info->pvtpll_min_rate);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = of_property_read_u32(np, "rockchip,pvtpll-volt-step", &info->pvtpll_volt_step);
|
|
out:
|
|
of_node_put(np);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_init_pvtpll_info(struct rockchip_opp_info *info)
|
|
{
|
|
struct opp_table *opp_table;
|
|
struct dev_pm_opp *opp;
|
|
int i = 0, max_count, ret;
|
|
|
|
ret = rockchip_pvtpll_parse_dt(info);
|
|
if (ret)
|
|
return ret;
|
|
|
|
max_count = dev_pm_opp_get_opp_count(info->dev);
|
|
if (max_count <= 0)
|
|
return max_count ? max_count : -ENODATA;
|
|
|
|
info->opp_table = kcalloc(max_count, sizeof(*info->opp_table), GFP_KERNEL);
|
|
if (!info->opp_table)
|
|
return -ENOMEM;
|
|
|
|
opp_table = dev_pm_opp_get_opp_table(info->dev);
|
|
if (IS_ERR(opp_table)) {
|
|
kfree(info->opp_table);
|
|
info->opp_table = NULL;
|
|
return PTR_ERR(opp_table);
|
|
}
|
|
|
|
mutex_lock(&opp_table->lock);
|
|
list_for_each_entry(opp, &opp_table->opp_list, node) {
|
|
if (!opp->available)
|
|
continue;
|
|
|
|
info->opp_table[i].u_volt = opp->supplies[0].u_volt;
|
|
info->opp_table[i].u_volt_min = opp->supplies[0].u_volt_min;
|
|
info->opp_table[i].u_volt_max = opp->supplies[0].u_volt_max;
|
|
if (opp_table->regulator_count > 1) {
|
|
info->opp_table[i].u_volt_mem = opp->supplies[1].u_volt;
|
|
info->opp_table[i].u_volt_mem_min = opp->supplies[1].u_volt_min;
|
|
info->opp_table[i].u_volt_mem_max = opp->supplies[1].u_volt_max;
|
|
}
|
|
info->opp_table[i++].rate = opp->rates[0];
|
|
}
|
|
mutex_unlock(&opp_table->lock);
|
|
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_pvtpll_set_volt(struct device *dev, struct regulator *reg,
|
|
int target_uV, int max_uV, char *reg_name)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = regulator_set_voltage(reg, target_uV, max_uV);
|
|
if (ret)
|
|
dev_err(dev, "%s: failed to set %s voltage (%d %d uV): %d\n",
|
|
__func__, reg_name, target_uV, max_uV, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_pvtpll_set_clk(struct device *dev, struct clk *clk,
|
|
unsigned long rate)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = clk_set_rate(clk, rate);
|
|
if (ret)
|
|
dev_err(dev, "%s: failed to set rate %lu Hz, ret:%d\n",
|
|
__func__, rate, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void rockchip_pvtpll_calibrate_opp(struct rockchip_opp_info *info)
|
|
{
|
|
struct opp_table *opp_table;
|
|
struct dev_pm_opp *opp;
|
|
struct regulator *reg = NULL, *reg_mem = NULL;
|
|
unsigned long old_volt = 0, old_volt_mem = 0;
|
|
unsigned long volt = 0, volt_mem = 0;
|
|
unsigned long volt_min, volt_max, volt_mem_min, volt_mem_max;
|
|
unsigned long rate, pvtpll_rate, old_rate, cur_rate, delta0, delta1;
|
|
int i = 0, max_count, step, cur_step, ret;
|
|
|
|
if (!info || !info->pvtpll_base)
|
|
return;
|
|
|
|
dev_dbg(info->dev, "calibrating opp ...\n");
|
|
ret = rockchip_init_pvtpll_info(info);
|
|
if (ret)
|
|
return;
|
|
|
|
max_count = dev_pm_opp_get_opp_count(info->dev);
|
|
if (max_count <= 0)
|
|
return;
|
|
|
|
opp_table = dev_pm_opp_get_opp_table(info->dev);
|
|
if (IS_ERR(opp_table))
|
|
return;
|
|
|
|
if (info->clocks) {
|
|
ret = clk_bulk_prepare_enable(info->nclocks, info->clocks);
|
|
if (ret) {
|
|
dev_err(info->dev, "failed to enable opp clks\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((!opp_table->regulators) || IS_ERR(opp_table->clk))
|
|
goto out_put;
|
|
|
|
reg = opp_table->regulators[0];
|
|
old_volt = regulator_get_voltage(reg);
|
|
if (opp_table->regulator_count > 1) {
|
|
reg_mem = opp_table->regulators[1];
|
|
old_volt_mem = regulator_get_voltage(reg_mem);
|
|
if (IS_ERR_VALUE(old_volt_mem))
|
|
goto out_put;
|
|
}
|
|
old_rate = clk_get_rate(opp_table->clk);
|
|
if (IS_ERR_VALUE(old_volt) || IS_ERR_VALUE(old_rate))
|
|
goto out_put;
|
|
cur_rate = old_rate;
|
|
|
|
step = regulator_get_linear_step(reg);
|
|
if (!step || info->pvtpll_volt_step > step)
|
|
step = info->pvtpll_volt_step;
|
|
|
|
if (old_rate > info->pvtpll_min_rate * 1000) {
|
|
if (rockchip_pvtpll_set_clk(info->dev, opp_table->clk,
|
|
info->pvtpll_min_rate * 1000))
|
|
goto out_put;
|
|
}
|
|
|
|
for (i = 0; i < max_count; i++) {
|
|
rate = info->opp_table[i].rate;
|
|
if (rate < 1000 * info->pvtpll_min_rate)
|
|
continue;
|
|
|
|
volt = max(volt, info->opp_table[i].u_volt);
|
|
volt_min = info->opp_table[i].u_volt_min;
|
|
volt_max = info->opp_table[i].u_volt_max;
|
|
|
|
if (opp_table->regulator_count > 1) {
|
|
volt_mem = max(volt_mem, info->opp_table[i].u_volt_mem);
|
|
volt_mem_min = info->opp_table[i].u_volt_mem_min;
|
|
volt_mem_max = info->opp_table[i].u_volt_mem_max;
|
|
if (rockchip_pvtpll_set_volt(info->dev, reg_mem,
|
|
volt_mem, volt_mem_max, "mem"))
|
|
goto out;
|
|
}
|
|
if (rockchip_pvtpll_set_volt(info->dev, reg, volt, volt_max, "vdd"))
|
|
goto out;
|
|
|
|
if (rockchip_pvtpll_set_clk(info->dev, opp_table->clk, rate))
|
|
goto out;
|
|
cur_rate = rate;
|
|
pvtpll_rate = rockchip_pvtpll_get_rate(info);
|
|
if (!pvtpll_rate)
|
|
goto out;
|
|
cur_step = (pvtpll_rate < rate) ? step : -step;
|
|
delta1 = abs(pvtpll_rate - rate);
|
|
do {
|
|
delta0 = delta1;
|
|
volt += cur_step;
|
|
if ((volt < volt_min) || (volt > volt_max))
|
|
break;
|
|
if (opp_table->regulator_count > 1) {
|
|
if (volt > volt_mem_max)
|
|
break;
|
|
else if (volt < volt_mem_min)
|
|
volt_mem = volt_mem_min;
|
|
else
|
|
volt_mem = volt;
|
|
if (rockchip_pvtpll_set_volt(info->dev, reg_mem,
|
|
volt_mem, volt_mem_max,
|
|
"mem"))
|
|
break;
|
|
}
|
|
if (rockchip_pvtpll_set_volt(info->dev, reg, volt,
|
|
volt_max, "vdd"))
|
|
break;
|
|
pvtpll_rate = rockchip_pvtpll_get_rate(info);
|
|
if (!pvtpll_rate)
|
|
goto out;
|
|
delta1 = abs(pvtpll_rate - rate);
|
|
} while (delta1 < delta0);
|
|
|
|
volt -= cur_step;
|
|
info->opp_table[i].u_volt = volt;
|
|
if (opp_table->regulator_count > 1) {
|
|
if (volt < volt_mem_min)
|
|
volt_mem = volt_mem_min;
|
|
else
|
|
volt_mem = volt;
|
|
info->opp_table[i].u_volt_mem = volt_mem;
|
|
}
|
|
}
|
|
|
|
i = 0;
|
|
mutex_lock(&opp_table->lock);
|
|
list_for_each_entry(opp, &opp_table->opp_list, node) {
|
|
if (!opp->available)
|
|
continue;
|
|
|
|
opp->supplies[0].u_volt = info->opp_table[i].u_volt;
|
|
if (opp_table->regulator_count > 1)
|
|
opp->supplies[1].u_volt = info->opp_table[i].u_volt_mem;
|
|
i++;
|
|
}
|
|
mutex_unlock(&opp_table->lock);
|
|
dev_info(info->dev, "opp calibration done\n");
|
|
out:
|
|
if (cur_rate > old_rate)
|
|
rockchip_pvtpll_set_clk(info->dev, opp_table->clk, old_rate);
|
|
if (opp_table->regulator_count > 1)
|
|
rockchip_pvtpll_set_volt(info->dev, reg_mem, old_volt_mem,
|
|
INT_MAX, "mem");
|
|
rockchip_pvtpll_set_volt(info->dev, reg, old_volt, INT_MAX, "vdd");
|
|
if (cur_rate < old_rate)
|
|
rockchip_pvtpll_set_clk(info->dev, opp_table->clk, old_rate);
|
|
out_put:
|
|
if (info->clocks)
|
|
clk_bulk_disable_unprepare(info->nclocks, info->clocks);
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
}
|
|
|
|
static void rockchip_pvtpll_add_length(struct rockchip_opp_info *info)
|
|
{
|
|
struct device_node *np;
|
|
struct opp_table *opp_table;
|
|
struct dev_pm_opp *opp;
|
|
unsigned long old_rate;
|
|
unsigned int min_rate = 0, max_rate = 0, margin = 0;
|
|
u32 opp_flag = 0;
|
|
int ret;
|
|
|
|
if (!info)
|
|
return;
|
|
|
|
np = of_parse_phandle(info->dev->of_node, "operating-points-v2", 0);
|
|
if (!np) {
|
|
dev_warn(info->dev, "OPP-v2 not supported\n");
|
|
return;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "rockchip,pvtpll-len-min-rate", &min_rate))
|
|
goto out;
|
|
if (of_property_read_u32(np, "rockchip,pvtpll-len-max-rate", &max_rate))
|
|
goto out;
|
|
if (of_property_read_u32(np, "rockchip,pvtpll-len-margin", &margin))
|
|
goto out;
|
|
|
|
opp_table = dev_pm_opp_get_opp_table(info->dev);
|
|
if (IS_ERR(opp_table))
|
|
goto out;
|
|
old_rate = clk_get_rate(opp_table->clk);
|
|
opp_flag = OPP_ADD_LENGTH | ((margin & OPP_LENGTH_MASK) << OPP_LENGTH_SHIFT);
|
|
|
|
mutex_lock(&opp_table->lock);
|
|
list_for_each_entry(opp, &opp_table->opp_list, node) {
|
|
if (opp->rates[0] < min_rate * 1000 || opp->rates[0] > max_rate * 1000)
|
|
continue;
|
|
ret = clk_set_rate(opp_table->clk, opp->rates[0] | opp_flag);
|
|
if (ret) {
|
|
dev_err(info->dev,
|
|
"failed to change %lu len margin %d\n",
|
|
opp->rates[0], margin);
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&opp_table->lock);
|
|
|
|
clk_set_rate(opp_table->clk, old_rate);
|
|
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
out:
|
|
of_node_put(np);
|
|
}
|
|
|
|
static int rockchip_get_pvtm_pvtpll(struct device *dev, struct device_node *np,
|
|
struct rockchip_opp_info *info,
|
|
const char *reg_name)
|
|
{
|
|
struct regulator *reg;
|
|
struct clk *clk;
|
|
struct pvtm_config *pvtm;
|
|
unsigned long old_freq;
|
|
unsigned int old_volt;
|
|
int cur_temp, diff_temp, prop_temp, diff_value;
|
|
int pvtm_value = 0;
|
|
int ret = 0;
|
|
|
|
if (!rockchip_nvmem_cell_read_u16(np, "pvtm", (u16 *)&pvtm_value) && pvtm_value) {
|
|
dev_info(dev, "pvtm = %d, get from otp\n", pvtm_value);
|
|
return pvtm_value;
|
|
}
|
|
|
|
if (!info || !info->pvtpll_base)
|
|
return -ENOMEM;
|
|
|
|
pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL);
|
|
if (!pvtm)
|
|
return -ENOMEM;
|
|
|
|
ret = rockchip_parse_pvtm_config(np, pvtm);
|
|
if (ret)
|
|
goto out;
|
|
|
|
clk = clk_get(dev, NULL);
|
|
if (IS_ERR_OR_NULL(clk)) {
|
|
dev_warn(dev, "Failed to get clk\n");
|
|
goto out;
|
|
}
|
|
|
|
reg = regulator_get_optional(dev, reg_name);
|
|
if (IS_ERR_OR_NULL(reg)) {
|
|
dev_warn(dev, "Failed to get reg\n");
|
|
clk_put(clk);
|
|
goto out;
|
|
}
|
|
old_freq = clk_get_rate(clk);
|
|
old_volt = regulator_get_voltage(reg);
|
|
|
|
ret = clk_set_rate(clk, pvtm->freq * 1000);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to set pvtm freq\n");
|
|
goto put_reg;
|
|
}
|
|
ret = regulator_set_voltage(reg, pvtm->volt, INT_MAX);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to set pvtm_volt\n");
|
|
goto restore_clk;
|
|
}
|
|
usleep_range(pvtm->sample_time, pvtm->sample_time + 100);
|
|
|
|
ret = regmap_read(info->pvtpll_base, pvtm->offset, &pvtm_value);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to get pvtm from 0x%x\n", pvtm->offset);
|
|
goto resetore_volt;
|
|
}
|
|
pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp);
|
|
diff_temp = (cur_temp / 1000 - pvtm->ref_temp);
|
|
if (diff_temp < 0)
|
|
prop_temp = pvtm->temp_prop[0];
|
|
else
|
|
prop_temp = pvtm->temp_prop[1];
|
|
diff_value = diff_temp * prop_temp / 1000;
|
|
pvtm_value += diff_value;
|
|
|
|
dev_info(dev, "pvtm=%d\n", pvtm_value);
|
|
|
|
resetore_volt:
|
|
regulator_set_voltage(reg, old_volt, INT_MAX);
|
|
restore_clk:
|
|
clk_set_rate(clk, old_freq);
|
|
put_reg:
|
|
regulator_put(reg);
|
|
clk_put(clk);
|
|
out:
|
|
kfree(pvtm);
|
|
|
|
return pvtm_value;
|
|
}
|
|
|
|
static int rockchip_get_pvtm(struct device *dev, struct device_node *np,
|
|
const char *reg_name)
|
|
{
|
|
struct regulator *reg;
|
|
struct clk *clk;
|
|
unsigned int ch[2];
|
|
int pvtm = 0;
|
|
u16 tmp = 0;
|
|
|
|
if (!rockchip_nvmem_cell_read_u16(np, "pvtm", &tmp) && tmp) {
|
|
pvtm = 10 * tmp;
|
|
dev_info(dev, "pvtm = %d, from nvmem\n", pvtm);
|
|
return pvtm;
|
|
}
|
|
|
|
if (of_property_read_u32_array(np, "rockchip,pvtm-ch", ch, 2))
|
|
return -EINVAL;
|
|
|
|
if (ch[0] >= PVTM_CH_MAX || ch[1] >= PVTM_SUB_CH_MAX)
|
|
return -EINVAL;
|
|
|
|
if (pvtm_value[ch[0]][ch[1]]) {
|
|
dev_info(dev, "pvtm = %d, form pvtm_value\n", pvtm_value[ch[0]][ch[1]]);
|
|
return pvtm_value[ch[0]][ch[1]];
|
|
}
|
|
|
|
clk = clk_get(dev, NULL);
|
|
if (IS_ERR_OR_NULL(clk)) {
|
|
dev_warn(dev, "Failed to get clk\n");
|
|
return PTR_ERR_OR_ZERO(clk);
|
|
}
|
|
|
|
reg = regulator_get_optional(dev, reg_name);
|
|
if (IS_ERR_OR_NULL(reg)) {
|
|
dev_warn(dev, "Failed to get reg\n");
|
|
clk_put(clk);
|
|
return PTR_ERR_OR_ZERO(reg);
|
|
}
|
|
|
|
rockchip_get_pvtm_specific_value(dev, np, clk, reg, &pvtm);
|
|
|
|
regulator_put(reg);
|
|
clk_put(clk);
|
|
|
|
return pvtm;
|
|
}
|
|
|
|
static void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np,
|
|
struct rockchip_opp_info *info,
|
|
const char *reg_name, int *volt_sel,
|
|
int *scale_sel)
|
|
{
|
|
struct property *prop = NULL;
|
|
char name[NAME_MAX];
|
|
int pvtm, ret;
|
|
u32 hw = 0;
|
|
|
|
if (of_property_read_bool(np, "rockchip,pvtm-pvtpll"))
|
|
pvtm = rockchip_get_pvtm_pvtpll(dev, np, info, reg_name);
|
|
else
|
|
pvtm = rockchip_get_pvtm(dev, np, reg_name);
|
|
if (pvtm <= 0)
|
|
return;
|
|
|
|
if (!volt_sel)
|
|
goto next;
|
|
if (info->process >= 0) {
|
|
snprintf(name, sizeof(name),
|
|
"rockchip,p%d-pvtm-voltage-sel", info->process);
|
|
prop = of_find_property(np, name, NULL);
|
|
} else if (info->bin > 0) {
|
|
of_property_read_u32(np, "rockchip,pvtm-hw", &hw);
|
|
if (hw && (hw & BIT(info->bin))) {
|
|
sprintf(name, "rockchip,pvtm-voltage-sel-hw");
|
|
prop = of_find_property(np, name, NULL);
|
|
}
|
|
if (!prop) {
|
|
snprintf(name, sizeof(name),
|
|
"rockchip,pvtm-voltage-sel-B%d", info->bin);
|
|
prop = of_find_property(np, name, NULL);
|
|
}
|
|
}
|
|
if (!prop)
|
|
sprintf(name, "rockchip,pvtm-voltage-sel");
|
|
ret = rockchip_get_sel(np, name, pvtm, volt_sel);
|
|
if (!ret && volt_sel)
|
|
dev_info(dev, "pvtm-volt-sel=%d\n", *volt_sel);
|
|
|
|
next:
|
|
if (!scale_sel)
|
|
return;
|
|
prop = NULL;
|
|
if (info->process >= 0) {
|
|
snprintf(name, sizeof(name),
|
|
"rockchip,p%d-pvtm-scaling-sel", info->process);
|
|
prop = of_find_property(np, name, NULL);
|
|
}
|
|
if (!prop)
|
|
sprintf(name, "rockchip,pvtm-scaling-sel");
|
|
ret = rockchip_get_sel(np, name, pvtm, scale_sel);
|
|
if (!ret)
|
|
dev_info(dev, "pvtm-scale=%d\n", *scale_sel);
|
|
}
|
|
|
|
static void rockchip_of_get_bin_sel(struct device *dev, struct device_node *np,
|
|
int bin, int *scale_sel)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!scale_sel || bin < 0)
|
|
return;
|
|
|
|
ret = rockchip_get_bin_sel(np, "rockchip,bin-scaling-sel",
|
|
bin, scale_sel);
|
|
if (!ret)
|
|
dev_info(dev, "bin-scale=%d\n", *scale_sel);
|
|
}
|
|
|
|
static void rockchip_of_get_bin_volt_sel(struct device *dev, struct device_node *np,
|
|
int bin, int *bin_volt_sel)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!bin_volt_sel || bin < 0)
|
|
return;
|
|
|
|
ret = rockchip_get_bin_sel(np, "rockchip,bin-voltage-sel",
|
|
bin, bin_volt_sel);
|
|
if (!ret)
|
|
dev_info(dev, "bin-volt-sel=%d\n", *bin_volt_sel);
|
|
}
|
|
|
|
void rockchip_get_opp_data(const struct of_device_id *matches,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
const struct of_device_id *match;
|
|
struct device_node *node;
|
|
|
|
node = of_find_node_by_path("/");
|
|
match = of_match_node(matches, node);
|
|
if (match && match->data)
|
|
info->data = match->data;
|
|
of_node_put(node);
|
|
}
|
|
EXPORT_SYMBOL(rockchip_get_opp_data);
|
|
|
|
static int rockchip_get_volt_rm_table(struct device *dev, struct device_node *np,
|
|
char *porp_name, struct volt_rm_table **table)
|
|
{
|
|
struct volt_rm_table *rm_table;
|
|
const struct property *prop;
|
|
int count, i;
|
|
|
|
prop = of_find_property(np, porp_name, NULL);
|
|
if (!prop)
|
|
return -EINVAL;
|
|
|
|
if (!prop->value)
|
|
return -ENODATA;
|
|
|
|
count = of_property_count_u32_elems(np, porp_name);
|
|
if (count < 0)
|
|
return -EINVAL;
|
|
|
|
if (count % 2)
|
|
return -EINVAL;
|
|
|
|
rm_table = devm_kzalloc(dev, sizeof(*rm_table) * (count / 2 + 1),
|
|
GFP_KERNEL);
|
|
if (!rm_table)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < count / 2; i++) {
|
|
of_property_read_u32_index(np, porp_name, 2 * i,
|
|
&rm_table[i].volt);
|
|
of_property_read_u32_index(np, porp_name, 2 * i + 1,
|
|
&rm_table[i].rm);
|
|
}
|
|
|
|
rm_table[i].volt = 0;
|
|
rm_table[i].rm = VOLT_RM_TABLE_END;
|
|
|
|
*table = rm_table;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_get_soc_info(struct device *dev, struct device_node *np,
|
|
int *bin, int *process)
|
|
{
|
|
u8 value = 0;
|
|
int ret = 0;
|
|
|
|
if (*bin >= 0 || *process >= 0)
|
|
return 0;
|
|
|
|
if (of_property_match_string(np, "nvmem-cell-names",
|
|
"remark_spec_serial_number") >= 0)
|
|
rockchip_nvmem_cell_read_u8(np, "remark_spec_serial_number", &value);
|
|
|
|
if (!value && of_property_match_string(np, "nvmem-cell-names",
|
|
"specification_serial_number") >= 0) {
|
|
ret = rockchip_nvmem_cell_read_u8(np,
|
|
"specification_serial_number",
|
|
&value);
|
|
if (ret) {
|
|
dev_err(dev,
|
|
"Failed to get specification_serial_number\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* M */
|
|
if (value == 0xd)
|
|
*bin = 1;
|
|
/* J */
|
|
else if (value == 0xa)
|
|
*bin = 2;
|
|
/* S */
|
|
else if (value == 0x13)
|
|
*bin = 3;
|
|
|
|
if (*bin < 0)
|
|
*bin = 0;
|
|
dev_info(dev, "bin=%d\n", *bin);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rockchip_init_pvtpll_table(struct device *dev,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
struct device_node *np = NULL;
|
|
struct property *prop = NULL;
|
|
struct of_phandle_args clkspec = { 0 };
|
|
struct arm_smccc_res res;
|
|
char prop_name[NAME_MAX];
|
|
u32 *value;
|
|
int count;
|
|
int ret, i;
|
|
|
|
if (!info)
|
|
return;
|
|
|
|
np = of_parse_phandle(dev->of_node, "operating-points-v2", 0);
|
|
if (!np) {
|
|
dev_warn(dev, "OPP-v2 not supported\n");
|
|
return;
|
|
}
|
|
|
|
ret = of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells",
|
|
0, &clkspec);
|
|
if (ret)
|
|
goto out;
|
|
info->pvtpll_clk_id = clkspec.args[0];
|
|
of_node_put(clkspec.np);
|
|
|
|
res = sip_smc_get_pvtpll_info(PVTPLL_GET_INFO, info->pvtpll_clk_id);
|
|
if (res.a0) {
|
|
info->pvtpll_smc = false;
|
|
goto out;
|
|
}
|
|
if (!res.a1)
|
|
info->pvtpll_low_temp = true;
|
|
|
|
if (info->bin > 0) {
|
|
snprintf(prop_name, sizeof(prop_name),
|
|
"rockchip,pvtpll-table-B%d", info->bin);
|
|
prop = of_find_property(np, prop_name, NULL);
|
|
}
|
|
if (!prop)
|
|
sprintf(prop_name, "rockchip,pvtpll-table");
|
|
|
|
prop = of_find_property(np, prop_name, NULL);
|
|
if (!prop)
|
|
goto out;
|
|
|
|
count = of_property_count_u32_elems(np, prop_name);
|
|
if (count < 0) {
|
|
dev_err(dev, "%s: Invalid %s property (%d)\n", __func__,
|
|
prop_name, count);
|
|
goto out;
|
|
} else if (count % 5) {
|
|
dev_err(dev, "Invalid count of %s\n", prop_name);
|
|
goto out;
|
|
}
|
|
|
|
value = kmalloc_array(count, sizeof(*value), GFP_KERNEL);
|
|
if (!value)
|
|
goto out;
|
|
ret = of_property_read_u32_array(np, prop_name, value, count);
|
|
if (ret) {
|
|
dev_err(dev, "%s: error parsing %s: %d\n", __func__,
|
|
prop_name, ret);
|
|
goto free_value;
|
|
}
|
|
|
|
for (i = 0; i < count; i += 5) {
|
|
res = sip_smc_pvtpll_config(PVTPLL_ADJUST_TABLE,
|
|
info->pvtpll_clk_id, value[i],
|
|
value[i + 1], value[i + 2],
|
|
value[i + 3], value[i + 4]);
|
|
if (res.a0) {
|
|
dev_err(dev,
|
|
"%s: error cfg clk_id=%u %u %u %u %u %u (%d)\n",
|
|
__func__, info->pvtpll_clk_id, value[i],
|
|
value[i + 1], value[i + 2], value[i + 3],
|
|
value[i + 4], (int)res.a0);
|
|
goto free_value;
|
|
}
|
|
}
|
|
|
|
free_value:
|
|
kfree(value);
|
|
out:
|
|
of_node_put(np);
|
|
}
|
|
|
|
static int rockchip_set_opp_supported_hw(struct device *dev,
|
|
struct device_node *np,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
u32 version = 0, speed = 0;
|
|
|
|
if (!of_property_read_bool(np, "rockchip,supported-hw"))
|
|
return 0;
|
|
if (info->supported_hw[0] || info->supported_hw[1])
|
|
return 0;
|
|
|
|
if (info->bin >= 0)
|
|
version = info->bin;
|
|
if (info->volt_sel >= 0)
|
|
speed = info->volt_sel;
|
|
/* SoC Version */
|
|
info->supported_hw[0] = BIT(version);
|
|
/* Speed Grade */
|
|
info->supported_hw[1] = BIT(speed);
|
|
|
|
dev_info(dev, "soc version=%d, speed=%d\n", version, speed);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rockchip_get_scale_volt_sel(struct device *dev, char *lkg_name,
|
|
const char *reg_name,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
struct device_node *np;
|
|
int lkg_scale = 0, pvtm_scale = 0, bin_scale = 0;
|
|
int lkg_volt_sel = -EINVAL, pvtm_volt_sel = -EINVAL;
|
|
int bin_volt_sel = -EINVAL;
|
|
|
|
np = of_parse_phandle(dev->of_node, "operating-points-v2", 0);
|
|
if (!np) {
|
|
dev_warn(dev, "OPP-v2 not supported\n");
|
|
return;
|
|
}
|
|
|
|
rockchip_of_get_lkg_sel(dev, np, lkg_name, info->process,
|
|
&lkg_volt_sel, &lkg_scale);
|
|
rockchip_of_get_pvtm_sel(dev, np, info, reg_name, &pvtm_volt_sel,
|
|
&pvtm_scale);
|
|
rockchip_of_get_bin_sel(dev, np, info->bin, &bin_scale);
|
|
rockchip_of_get_bin_volt_sel(dev, np, info->bin, &bin_volt_sel);
|
|
info->scale = max3(lkg_scale, pvtm_scale, bin_scale);
|
|
if (bin_volt_sel >= 0)
|
|
info->volt_sel = bin_volt_sel;
|
|
else
|
|
info->volt_sel = max(lkg_volt_sel, pvtm_volt_sel);
|
|
|
|
of_node_put(np);
|
|
}
|
|
|
|
static int rockchip_opp_set_regulator_helper(struct device *dev,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
struct opp_table *opp_table;
|
|
|
|
if (!info || !info->data || !info->data->config_regulators)
|
|
return 0;
|
|
|
|
opp_table = dev_pm_opp_get_opp_table(dev);
|
|
if (IS_ERR(opp_table))
|
|
return PTR_ERR(opp_table);
|
|
|
|
opp_table->config_regulators = info->data->config_regulators;
|
|
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_opp_set_config(struct device *dev, struct rockchip_opp_info *info,
|
|
const char *clk_name, const char *reg_name)
|
|
{
|
|
char name[MAX_PROP_NAME_LEN];
|
|
struct dev_pm_opp_config config = {0};
|
|
struct clk *clk = NULL;
|
|
const char *reg_names[] = {NULL, NULL, NULL};
|
|
const char *clk_names[] = {NULL, NULL, NULL};
|
|
|
|
if (clk_name) {
|
|
clk = clk_get(dev, clk_name);
|
|
if (IS_ERR_OR_NULL(clk)) {
|
|
if (!of_property_read_string_index(dev->of_node,
|
|
"clock-names",
|
|
0, &clk_name))
|
|
clk = clk_get(dev, clk_name);
|
|
}
|
|
if (!IS_ERR_OR_NULL(clk)) {
|
|
if (strstr(__clk_get_name(clk), "scmi"))
|
|
info->is_scmi_clk = true;
|
|
clk_names[0] = clk_name;
|
|
config.clk_names = clk_names;
|
|
clk_put(clk);
|
|
if (info->data && info->data->config_clks)
|
|
config.config_clks = info->data->config_clks;
|
|
}
|
|
}
|
|
|
|
if (info->process >= 0) {
|
|
if (info->volt_sel >= 0)
|
|
snprintf(name, MAX_PROP_NAME_LEN, "P%d-L%d",
|
|
info->process, info->volt_sel);
|
|
else
|
|
snprintf(name, MAX_PROP_NAME_LEN, "P%d", info->process);
|
|
config.prop_name = name;
|
|
} else if (info->volt_sel >= 0) {
|
|
snprintf(name, MAX_PROP_NAME_LEN, "L%d", info->volt_sel);
|
|
config.prop_name = name;
|
|
}
|
|
|
|
if (info->data && info->data->config_regulators)
|
|
config.config_regulators = info->data->config_regulators;
|
|
|
|
if (info->supported_hw[0] || info->supported_hw[1]) {
|
|
config.supported_hw = kmemdup(info->supported_hw, 2 * sizeof(u32),
|
|
GFP_KERNEL);
|
|
config.supported_hw_count = 2;
|
|
}
|
|
|
|
if (reg_name) {
|
|
reg_names[0] = reg_name;
|
|
if (of_find_property(dev->of_node, "mem-supply", NULL))
|
|
reg_names[1] = "mem";
|
|
config.regulator_names = reg_names;
|
|
}
|
|
|
|
info->opp_token = dev_pm_opp_set_config(dev, &config);
|
|
if (info->opp_token < 0) {
|
|
dev_err(dev, "failed to set opp config\n");
|
|
return info->opp_token;
|
|
}
|
|
|
|
/*
|
|
* The dev_pm_opp_set_config() only support setting regulator helper
|
|
* for multiple regulators, but on some platforms, still need to set
|
|
* regulator helper for single regulator.
|
|
*/
|
|
if (rockchip_opp_set_regulator_helper(dev, info)) {
|
|
dev_err(dev, "failed to set opp regulator helper\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rockchip_opp_dvfs_lock(struct rockchip_opp_info *info)
|
|
{
|
|
if (info)
|
|
mutex_lock(&info->dvfs_mutex);
|
|
}
|
|
EXPORT_SYMBOL(rockchip_opp_dvfs_lock);
|
|
|
|
void rockchip_opp_dvfs_unlock(struct rockchip_opp_info *info)
|
|
{
|
|
if (info)
|
|
mutex_unlock(&info->dvfs_mutex);
|
|
}
|
|
EXPORT_SYMBOL(rockchip_opp_dvfs_unlock);
|
|
|
|
static int rockchip_get_opp_clk(struct device *dev, struct device_node *np,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
struct clk_bulk_data *clocks;
|
|
struct of_phandle_args clkspec;
|
|
int ret = 0, nclocks = 0, i;
|
|
|
|
if (of_find_property(np, "rockchip,opp-clocks", NULL)) {
|
|
nclocks = of_count_phandle_with_args(np, "rockchip,opp-clocks",
|
|
"#clock-cells");
|
|
if (nclocks <= 0)
|
|
return 0;
|
|
clocks = devm_kcalloc(dev, nclocks, sizeof(*clocks), GFP_KERNEL);
|
|
if (!clocks)
|
|
return -ENOMEM;
|
|
for (i = 0; i < nclocks; i++) {
|
|
ret = of_parse_phandle_with_args(np,
|
|
"rockchip,opp-clocks",
|
|
"#clock-cells", i,
|
|
&clkspec);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: failed to parse opp clk %d\n",
|
|
np->name, i);
|
|
goto error;
|
|
}
|
|
clocks[i].clk = of_clk_get_from_provider(&clkspec);
|
|
of_node_put(clkspec.np);
|
|
if (IS_ERR(clocks[i].clk)) {
|
|
ret = PTR_ERR(clocks[i].clk);
|
|
clocks[i].clk = NULL;
|
|
dev_err(dev, "%s: failed to get opp clk %d\n",
|
|
np->name, i);
|
|
goto error;
|
|
}
|
|
}
|
|
} else {
|
|
nclocks = of_clk_get_parent_count(np);
|
|
if (nclocks <= 0)
|
|
return 0;
|
|
clocks = devm_kcalloc(dev, nclocks, sizeof(*clocks), GFP_KERNEL);
|
|
if (!clocks)
|
|
return -ENOMEM;
|
|
for (i = 0; i < nclocks; i++) {
|
|
clocks[i].clk = of_clk_get(np, i);
|
|
if (IS_ERR(clocks[i].clk)) {
|
|
ret = PTR_ERR(clocks[i].clk);
|
|
clocks[i].clk = NULL;
|
|
dev_err(dev, "%s: failed to get clk %d\n",
|
|
np->name, i);
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
info->clocks = clocks;
|
|
info->nclocks = nclocks;
|
|
|
|
return 0;
|
|
error:
|
|
while (--i >= 0)
|
|
clk_put(clocks[i].clk);
|
|
devm_kfree(dev, clocks);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int rockchip_init_opp_info(struct device *dev, struct rockchip_opp_info *info,
|
|
char *clk_name, char *reg_name)
|
|
{
|
|
struct device_node *np;
|
|
int ret = 0;
|
|
u32 freq;
|
|
|
|
/* Get OPP descriptor node */
|
|
np = of_parse_phandle(dev->of_node, "operating-points-v2", 0);
|
|
if (!np) {
|
|
dev_dbg(dev, "Failed to find operating-points-v2\n");
|
|
return -ENOENT;
|
|
}
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
info->dev = dev;
|
|
info->bin = -EINVAL;
|
|
info->process = -EINVAL;
|
|
info->volt_sel = -EINVAL;
|
|
info->pvtpll_clk_id = UINT_MAX;
|
|
info->pvtpll_smc = true;
|
|
info->is_runtime_active = true;
|
|
mutex_init(&info->dvfs_mutex);
|
|
|
|
of_property_read_u32(np, "rockchip,init-freq", &info->init_freq);
|
|
|
|
info->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
|
|
if (IS_ERR(info->grf))
|
|
info->grf = NULL;
|
|
info->dsu_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,dsu-grf");
|
|
if (IS_ERR(info->dsu_grf)) {
|
|
info->dsu_grf = NULL;
|
|
info->cci_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,cci-grf");
|
|
if (IS_ERR(info->cci_grf))
|
|
info->cci_grf = NULL;
|
|
}
|
|
|
|
info->pvtpll_base = syscon_regmap_lookup_by_phandle(np, "rockchip,pvtpll");
|
|
if (IS_ERR(info->pvtpll_base))
|
|
info->pvtpll_base = info->grf;
|
|
|
|
ret = rockchip_get_opp_clk(dev, np, info);
|
|
if (ret)
|
|
goto out;
|
|
if (info->clocks) {
|
|
ret = clk_bulk_prepare_enable(info->nclocks, info->clocks);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable opp clks\n");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (info->data && info->data->set_read_margin) {
|
|
info->current_rm = UINT_MAX;
|
|
info->target_rm = UINT_MAX;
|
|
rockchip_get_volt_rm_table(dev, np, "volt-mem-read-margin",
|
|
&info->volt_rm_tbl);
|
|
of_property_read_u32(np, "low-volt-mem-read-margin",
|
|
&info->low_rm);
|
|
if (!of_property_read_u32(np, "intermediate-threshold-freq",
|
|
&freq))
|
|
info->intermediate_threshold_freq = freq * 1000;
|
|
rockchip_init_read_margin(dev, info, reg_name);
|
|
}
|
|
|
|
if (info->data && info->data->get_soc_info)
|
|
info->data->get_soc_info(dev, np, &info->bin, &info->process);
|
|
rockchip_get_soc_info(dev, np, &info->bin, &info->process);
|
|
|
|
rockchip_init_pvtpll_table(dev, info);
|
|
rockchip_get_scale_volt_sel(dev, "leakage", reg_name, info);
|
|
|
|
if (info && info->data && info->data->set_soc_info)
|
|
info->data->set_soc_info(dev, np, info);
|
|
rockchip_set_opp_supported_hw(dev, np, info);
|
|
|
|
ret = rockchip_opp_set_config(dev, info, clk_name, reg_name);
|
|
|
|
if (info->clocks)
|
|
clk_bulk_disable_unprepare(info->nclocks, info->clocks);
|
|
out:
|
|
of_node_put(np);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_init_opp_info);
|
|
|
|
void rockchip_uninit_opp_info(struct device *dev, struct rockchip_opp_info *info)
|
|
{
|
|
dev_pm_opp_clear_config(info->opp_token);
|
|
}
|
|
EXPORT_SYMBOL(rockchip_uninit_opp_info);
|
|
|
|
static int rockchip_adjust_opp_by_irdrop(struct device *dev,
|
|
struct device_node *np,
|
|
unsigned long *safe_rate,
|
|
unsigned long *max_rate)
|
|
{
|
|
struct sel_table *irdrop_table = NULL;
|
|
struct opp_table *opp_table;
|
|
struct dev_pm_opp *opp;
|
|
unsigned long tmp_safe_rate = 0;
|
|
int evb_irdrop = 0, board_irdrop, delta_irdrop;
|
|
int opp_rate, i, ret = 0;
|
|
u32 max_volt = UINT_MAX;
|
|
bool reach_max_volt = false;
|
|
|
|
of_property_read_u32_index(np, "rockchip,max-volt", 0, &max_volt);
|
|
of_property_read_u32_index(np, "rockchip,evb-irdrop", 0, &evb_irdrop);
|
|
rockchip_get_sel_table(np, "rockchip,board-irdrop", &irdrop_table);
|
|
|
|
opp_table = dev_pm_opp_get_opp_table(dev);
|
|
if (IS_ERR(opp_table)) {
|
|
ret = PTR_ERR(opp_table);
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&opp_table->lock);
|
|
list_for_each_entry(opp, &opp_table->opp_list, node) {
|
|
if (!opp->available)
|
|
continue;
|
|
if (!irdrop_table) {
|
|
delta_irdrop = 0;
|
|
} else {
|
|
opp_rate = opp->rates[0] / 1000000;
|
|
board_irdrop = -EINVAL;
|
|
for (i = 0; irdrop_table[i].sel != SEL_TABLE_END; i++) {
|
|
if (opp_rate >= irdrop_table[i].min)
|
|
board_irdrop = irdrop_table[i].sel;
|
|
}
|
|
if (board_irdrop == -EINVAL)
|
|
delta_irdrop = 0;
|
|
else
|
|
delta_irdrop = board_irdrop - evb_irdrop;
|
|
}
|
|
if ((opp->supplies[0].u_volt + delta_irdrop) <= max_volt) {
|
|
opp->supplies[0].u_volt += delta_irdrop;
|
|
opp->supplies[0].u_volt_min += delta_irdrop;
|
|
if (opp->supplies[0].u_volt_max + delta_irdrop <=
|
|
max_volt)
|
|
opp->supplies[0].u_volt_max += delta_irdrop;
|
|
else
|
|
opp->supplies[0].u_volt_max = max_volt;
|
|
if (!reach_max_volt)
|
|
tmp_safe_rate = opp->rates[0];
|
|
if (opp->supplies[0].u_volt == max_volt)
|
|
reach_max_volt = true;
|
|
} else {
|
|
opp->supplies[0].u_volt = max_volt;
|
|
opp->supplies[0].u_volt_min = max_volt;
|
|
opp->supplies[0].u_volt_max = max_volt;
|
|
}
|
|
if (max_rate)
|
|
*max_rate = opp->rates[0];
|
|
if (safe_rate && tmp_safe_rate != opp->rates[0])
|
|
*safe_rate = tmp_safe_rate;
|
|
}
|
|
mutex_unlock(&opp_table->lock);
|
|
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
out:
|
|
kfree(irdrop_table);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void rockchip_adjust_opp_by_mbist_vmin(struct device *dev,
|
|
struct device_node *np)
|
|
{
|
|
struct opp_table *opp_table;
|
|
struct dev_pm_opp *opp;
|
|
u32 vmin = 0;
|
|
u8 index = 0;
|
|
|
|
if (rockchip_nvmem_cell_read_u8(np, "mbist-vmin", &index))
|
|
return;
|
|
|
|
if (!index)
|
|
return;
|
|
|
|
if (of_property_read_u32_index(np, "mbist-vmin", index-1, &vmin))
|
|
return;
|
|
|
|
opp_table = dev_pm_opp_get_opp_table(dev);
|
|
if (IS_ERR(opp_table))
|
|
return;
|
|
|
|
mutex_lock(&opp_table->lock);
|
|
list_for_each_entry(opp, &opp_table->opp_list, node) {
|
|
if (!opp->available)
|
|
continue;
|
|
if (opp->supplies->u_volt < vmin) {
|
|
opp->supplies->u_volt = vmin;
|
|
opp->supplies->u_volt_min = vmin;
|
|
}
|
|
}
|
|
mutex_unlock(&opp_table->lock);
|
|
}
|
|
|
|
static void rockchip_adjust_opp_by_otp(struct device *dev,
|
|
struct device_node *np)
|
|
{
|
|
struct dev_pm_opp *opp;
|
|
struct opp_table *opp_table;
|
|
struct otp_opp_info opp_info = {};
|
|
int ret;
|
|
|
|
ret = rockchip_nvmem_cell_read_common(np, "opp-info", &opp_info,
|
|
sizeof(opp_info));
|
|
if (ret || !opp_info.volt)
|
|
return;
|
|
|
|
dev_info(dev, "adjust opp-table by otp: min=%uM, max=%uM, volt=%umV\n",
|
|
opp_info.min_freq, opp_info.max_freq, opp_info.volt);
|
|
|
|
opp_table = dev_pm_opp_get_opp_table(dev);
|
|
if (IS_ERR(opp_table))
|
|
return;
|
|
|
|
mutex_lock(&opp_table->lock);
|
|
list_for_each_entry(opp, &opp_table->opp_list, node) {
|
|
if (!opp->available)
|
|
continue;
|
|
if (opp->rates[0] < opp_info.min_freq * 1000000)
|
|
continue;
|
|
if (opp->rates[0] > opp_info.max_freq * 1000000)
|
|
continue;
|
|
|
|
opp->supplies[0].u_volt += opp_info.volt * 1000;
|
|
if (opp->supplies[0].u_volt > opp->supplies[0].u_volt_max)
|
|
opp->supplies[0].u_volt = opp->supplies[0].u_volt_max;
|
|
if (opp_table->regulator_count > 1) {
|
|
opp->supplies[1].u_volt += opp_info.volt * 1000;
|
|
if (opp->supplies[1].u_volt > opp->supplies[1].u_volt_max)
|
|
opp->supplies[1].u_volt = opp->supplies[1].u_volt_max;
|
|
}
|
|
}
|
|
mutex_unlock(&opp_table->lock);
|
|
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
}
|
|
|
|
static int rockchip_adjust_opp_by_scale(struct device *dev,
|
|
unsigned long scale_rate)
|
|
{
|
|
struct dev_pm_opp *opp;
|
|
unsigned long rate;
|
|
int i, count, ret = 0;
|
|
|
|
count = dev_pm_opp_get_opp_count(dev);
|
|
if (count <= 0) {
|
|
ret = count ? count : -ENODATA;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0, rate = 0; i < count; i++, rate++) {
|
|
/* find next rate */
|
|
opp = dev_pm_opp_find_freq_ceil(dev, &rate);
|
|
if (IS_ERR(opp)) {
|
|
ret = PTR_ERR(opp);
|
|
goto out;
|
|
}
|
|
if (opp->rates[0] > scale_rate)
|
|
dev_pm_opp_disable(dev, opp->rates[0]);
|
|
dev_pm_opp_put(opp);
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_adjust_power_scale(struct device *dev, struct rockchip_opp_info *info)
|
|
{
|
|
struct device_node *np;
|
|
struct clk *clk;
|
|
unsigned long safe_rate = 0, max_rate = 0;
|
|
int irdrop_scale = 0, opp_scale = 0;
|
|
u32 target_scale, avs = 0, avs_scale = 0;
|
|
long scale_rate = 0;
|
|
int ret = 0;
|
|
|
|
np = of_parse_phandle(dev->of_node, "operating-points-v2", 0);
|
|
if (!np) {
|
|
dev_warn(dev, "OPP-v2 not supported\n");
|
|
return -ENOENT;
|
|
}
|
|
of_property_read_u32(np, "rockchip,avs-enable", &avs);
|
|
of_property_read_u32(np, "rockchip,avs", &avs);
|
|
of_property_read_u32(np, "rockchip,avs-scale", &avs_scale);
|
|
rockchip_adjust_opp_by_otp(dev, np);
|
|
rockchip_adjust_opp_by_mbist_vmin(dev, np);
|
|
rockchip_adjust_opp_by_irdrop(dev, np, &safe_rate, &max_rate);
|
|
|
|
dev_info(dev, "avs=%d\n", avs);
|
|
|
|
if (!safe_rate && !info->scale)
|
|
goto out_np;
|
|
|
|
clk = of_clk_get_by_name(np, NULL);
|
|
if (IS_ERR(clk)) {
|
|
if (!safe_rate)
|
|
goto out_np;
|
|
dev_dbg(dev, "Failed to get clk, safe_rate=%lu\n", safe_rate);
|
|
ret = rockchip_adjust_opp_by_scale(dev, safe_rate);
|
|
if (ret)
|
|
dev_err(dev, "Failed to adjust opp table\n");
|
|
goto out_np;
|
|
}
|
|
|
|
if (safe_rate)
|
|
irdrop_scale = rockchip_pll_clk_rate_to_scale(clk, safe_rate);
|
|
target_scale = max(irdrop_scale, info->scale);
|
|
if (target_scale <= 0)
|
|
goto out_clk;
|
|
dev_dbg(dev, "target_scale=%d, irdrop_scale=%d, scale=%d\n",
|
|
target_scale, irdrop_scale, info->scale);
|
|
|
|
if (max_rate)
|
|
opp_scale = rockchip_pll_clk_rate_to_scale(clk, max_rate);
|
|
if (avs == AVS_SCALING_RATE) {
|
|
ret = rockchip_pll_clk_adaptive_scaling(clk, target_scale);
|
|
if (ret)
|
|
dev_err(dev, "Failed to adaptive scaling\n");
|
|
if (opp_scale >= avs_scale)
|
|
goto out_clk;
|
|
dev_info(dev, "avs-scale=%d, opp-scale=%d\n", avs_scale,
|
|
opp_scale);
|
|
scale_rate = rockchip_pll_clk_scale_to_rate(clk, avs_scale);
|
|
if (scale_rate <= 0) {
|
|
dev_err(dev, "Failed to get avs scale rate, %d\n",
|
|
avs_scale);
|
|
goto out_clk;
|
|
}
|
|
dev_dbg(dev, "scale_rate=%lu\n", scale_rate);
|
|
ret = rockchip_adjust_opp_by_scale(dev, scale_rate);
|
|
if (ret)
|
|
dev_err(dev, "Failed to adjust opp table\n");
|
|
} else if (avs == AVS_DELETE_OPP) {
|
|
if (opp_scale >= target_scale)
|
|
goto out_clk;
|
|
dev_info(dev, "target_scale=%d, opp-scale=%d\n", target_scale,
|
|
opp_scale);
|
|
scale_rate = rockchip_pll_clk_scale_to_rate(clk, target_scale);
|
|
if (scale_rate <= 0) {
|
|
dev_err(dev, "Failed to get scale rate, %d\n",
|
|
target_scale);
|
|
goto out_clk;
|
|
}
|
|
dev_dbg(dev, "scale_rate=%lu\n", scale_rate);
|
|
ret = rockchip_adjust_opp_by_scale(dev, scale_rate);
|
|
if (ret)
|
|
dev_err(dev, "Failed to adjust opp table\n");
|
|
}
|
|
|
|
out_clk:
|
|
clk_put(clk);
|
|
out_np:
|
|
of_node_put(np);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_opp_parse_supplies(struct device *dev,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
struct opp_table *opp_table;
|
|
|
|
opp_table = dev_pm_opp_get_opp_table(dev);
|
|
if (IS_ERR(opp_table))
|
|
return PTR_ERR(opp_table);
|
|
|
|
if (opp_table->clk)
|
|
info->clk = opp_table->clk;
|
|
if (opp_table->regulators)
|
|
info->regulators = opp_table->regulators;
|
|
info->regulator_count = opp_table->regulator_count;
|
|
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_pvtpll_set_volt_sel(struct device *dev,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
struct arm_smccc_res res;
|
|
|
|
if (!info)
|
|
return 0;
|
|
if (info->volt_sel < 0)
|
|
return 0;
|
|
if (info->pvtpll_clk_id == UINT_MAX)
|
|
return 0;
|
|
|
|
if (!info->pvtpll_smc)
|
|
return rockchip_pvtpll_volt_sel_adjust(info->pvtpll_clk_id,
|
|
info->volt_sel);
|
|
|
|
res = sip_smc_pvtpll_config(PVTPLL_VOLT_SEL, info->pvtpll_clk_id,
|
|
(u32)info->volt_sel, 0, 0, 0, 0);
|
|
if (res.a0)
|
|
dev_err(dev, "%s: error cfg clk_id=%u voltsel (%d)\n", __func__,
|
|
info->pvtpll_clk_id, (int)res.a0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rockchip_adjust_opp_table(struct device *dev, struct rockchip_opp_info *info)
|
|
{
|
|
rockchip_opp_parse_supplies(dev, info);
|
|
rockchip_adjust_power_scale(dev, info);
|
|
rockchip_pvtpll_calibrate_opp(info);
|
|
rockchip_pvtpll_add_length(info);
|
|
rockchip_pvtpll_set_volt_sel(dev, info);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_adjust_opp_table);
|
|
|
|
int rockchip_get_read_margin(struct device *dev,
|
|
struct rockchip_opp_info *opp_info,
|
|
unsigned long volt, u32 *target_rm)
|
|
{
|
|
int i;
|
|
|
|
if (!opp_info || !opp_info->volt_rm_tbl)
|
|
return 0;
|
|
|
|
for (i = 0; opp_info->volt_rm_tbl[i].rm != VOLT_RM_TABLE_END; i++) {
|
|
if (volt >= opp_info->volt_rm_tbl[i].volt) {
|
|
opp_info->target_rm = opp_info->volt_rm_tbl[i].rm;
|
|
break;
|
|
}
|
|
}
|
|
*target_rm = opp_info->target_rm;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_get_read_margin);
|
|
|
|
int rockchip_set_read_margin(struct device *dev,
|
|
struct rockchip_opp_info *opp_info, u32 rm,
|
|
bool is_set_rm)
|
|
{
|
|
if (!is_set_rm || !opp_info)
|
|
return 0;
|
|
if (!opp_info || !opp_info->volt_rm_tbl)
|
|
return 0;
|
|
if (!opp_info->data || !opp_info->data->set_read_margin)
|
|
return 0;
|
|
if (rm == opp_info->current_rm)
|
|
return 0;
|
|
|
|
return opp_info->data->set_read_margin(dev, opp_info, rm);
|
|
}
|
|
EXPORT_SYMBOL(rockchip_set_read_margin);
|
|
|
|
static int rockchip_init_read_margin(struct device *dev,
|
|
struct rockchip_opp_info *opp_info,
|
|
const char *reg_name)
|
|
{
|
|
struct clk *clk;
|
|
struct regulator *reg;
|
|
unsigned long cur_rate;
|
|
int cur_volt, ret = 0;
|
|
u32 target_rm = UINT_MAX;
|
|
|
|
reg = regulator_get_optional(dev, reg_name);
|
|
if (IS_ERR(reg)) {
|
|
ret = PTR_ERR(reg);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "%s: no regulator (%s) found: %d\n",
|
|
__func__, reg_name, ret);
|
|
return ret;
|
|
}
|
|
cur_volt = regulator_get_voltage(reg);
|
|
if (cur_volt < 0) {
|
|
ret = cur_volt;
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "%s: failed to get (%s) volt: %d\n",
|
|
__func__, reg_name, ret);
|
|
goto out;
|
|
}
|
|
|
|
clk = clk_get(dev, NULL);
|
|
if (IS_ERR(clk)) {
|
|
ret = PTR_ERR(clk);
|
|
dev_err(dev, "%s: failed to get clk: %d\n", __func__, ret);
|
|
goto out;
|
|
}
|
|
cur_rate = clk_get_rate(clk);
|
|
|
|
rockchip_get_read_margin(dev, opp_info, cur_volt, &target_rm);
|
|
dev_dbg(dev, "cur_rate=%lu, threshold=%lu, cur_volt=%d, target_rm=%d\n",
|
|
cur_rate, opp_info->intermediate_threshold_freq,
|
|
cur_volt, target_rm);
|
|
if (opp_info->intermediate_threshold_freq &&
|
|
cur_rate > opp_info->intermediate_threshold_freq) {
|
|
clk_set_rate(clk, opp_info->intermediate_threshold_freq);
|
|
rockchip_set_read_margin(dev, opp_info, target_rm, true);
|
|
clk_set_rate(clk, cur_rate);
|
|
} else {
|
|
rockchip_set_read_margin(dev, opp_info, target_rm, true);
|
|
}
|
|
|
|
clk_put(clk);
|
|
out:
|
|
regulator_put(reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int rockchip_set_intermediate_rate(struct device *dev,
|
|
struct rockchip_opp_info *opp_info,
|
|
struct clk *clk, unsigned long old_freq,
|
|
unsigned long new_freq, bool is_scaling_up,
|
|
bool is_set_clk)
|
|
{
|
|
if (!is_set_clk)
|
|
return 0;
|
|
if (!opp_info || !opp_info->volt_rm_tbl)
|
|
return 0;
|
|
if (!opp_info->data || !opp_info->data->set_read_margin)
|
|
return 0;
|
|
if (opp_info->target_rm == opp_info->current_rm)
|
|
return 0;
|
|
/*
|
|
* There is no need to set intermediate rate if the new voltage
|
|
* and the current voltage are high voltage.
|
|
*/
|
|
if ((opp_info->target_rm < opp_info->low_rm) &&
|
|
(opp_info->current_rm < opp_info->low_rm))
|
|
return 0;
|
|
|
|
if (is_scaling_up) {
|
|
/*
|
|
* If scaling up and the current frequency is less than
|
|
* or equal to intermediate threshold frequency, there is
|
|
* no need to set intermediate rate.
|
|
*/
|
|
if (opp_info->intermediate_threshold_freq &&
|
|
old_freq <= opp_info->intermediate_threshold_freq)
|
|
return 0;
|
|
return clk_set_rate(clk, new_freq | OPP_SCALING_UP_INTER);
|
|
}
|
|
/*
|
|
* If scaling down and the new frequency is less than or equal to
|
|
* intermediate threshold frequency , there is no need to set
|
|
* intermediate rate and set the new frequency directly.
|
|
*/
|
|
if (opp_info->intermediate_threshold_freq &&
|
|
new_freq <= opp_info->intermediate_threshold_freq)
|
|
return clk_set_rate(clk, new_freq);
|
|
|
|
return clk_set_rate(clk, new_freq | OPP_SCALING_DOWN_INTER);
|
|
}
|
|
EXPORT_SYMBOL(rockchip_set_intermediate_rate);
|
|
|
|
int rockchip_opp_set_low_length(struct device *dev, struct device_node *np,
|
|
struct rockchip_opp_info *opp_info)
|
|
{
|
|
struct clk *clk;
|
|
unsigned long old_rate;
|
|
unsigned int low_len_sel;
|
|
u32 opp_flag = 0;
|
|
int ret = 0;
|
|
|
|
if (opp_info->volt_sel < 0)
|
|
return 0;
|
|
|
|
clk = clk_get(dev, NULL);
|
|
if (IS_ERR(clk)) {
|
|
dev_warn(dev, "failed to get cpu clk\n");
|
|
return PTR_ERR(clk);
|
|
}
|
|
|
|
/* low speed grade should change to low length */
|
|
if (of_property_read_u32(np, "rockchip,pvtm-low-len-sel",
|
|
&low_len_sel))
|
|
goto out;
|
|
if (opp_info->volt_sel > low_len_sel)
|
|
goto out;
|
|
opp_flag = OPP_LENGTH_LOW;
|
|
|
|
old_rate = clk_get_rate(clk);
|
|
ret = clk_set_rate(clk, old_rate | opp_flag);
|
|
if (ret) {
|
|
dev_err(dev, "failed to change length\n");
|
|
goto out;
|
|
}
|
|
clk_set_rate(clk, old_rate);
|
|
out:
|
|
clk_put(clk);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_opp_set_low_length);
|
|
|
|
static int rockchip_opp_set_volt(struct device *dev, struct regulator *reg,
|
|
struct dev_pm_opp_supply *supply, char *reg_name)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = regulator_set_voltage_triplet(reg, supply->u_volt_min,
|
|
supply->u_volt, supply->u_volt_max);
|
|
if (ret)
|
|
dev_err(dev, "%s: failed to set voltage (%lu %lu %lu uV): %d\n",
|
|
reg_name, supply->u_volt_min, supply->u_volt,
|
|
supply->u_volt_max, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int rockchip_opp_config_regulators(struct device *dev,
|
|
struct dev_pm_opp *old_opp,
|
|
struct dev_pm_opp *new_opp,
|
|
struct regulator **regulators,
|
|
unsigned int count,
|
|
struct rockchip_opp_info *info)
|
|
{
|
|
struct regulator *vdd_reg = regulators[0];
|
|
struct regulator *mem_reg;
|
|
struct dev_pm_opp_supply old_supplies[2] = { 0 };
|
|
struct dev_pm_opp_supply new_supplies[2] = { 0 };
|
|
unsigned long old_freq, freq;
|
|
u32 target_rm = UINT_MAX;
|
|
int ret = 0;
|
|
|
|
if (count > 1)
|
|
mem_reg = regulators[1];
|
|
|
|
ret = dev_pm_opp_get_supplies(new_opp, new_supplies);
|
|
if (ret)
|
|
return ret;
|
|
ret = dev_pm_opp_get_supplies(old_opp, old_supplies);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_bulk_prepare_enable(info->nclocks, info->clocks);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable opp clks\n");
|
|
return ret;
|
|
}
|
|
|
|
rockchip_get_read_margin(dev, info, new_supplies[0].u_volt, &target_rm);
|
|
|
|
old_freq = dev_pm_opp_get_freq(old_opp);
|
|
freq = dev_pm_opp_get_freq(new_opp);
|
|
|
|
if (count > 1)
|
|
dev_dbg(dev, "%lu %lu -> %lu %lu (uV)\n",
|
|
old_supplies[0].u_volt, old_supplies[1].u_volt,
|
|
new_supplies[0].u_volt, new_supplies[1].u_volt);
|
|
else
|
|
dev_dbg(dev, "%lu -> %lu (uV)\n", old_supplies[0].u_volt,
|
|
new_supplies[0].u_volt);
|
|
|
|
if (freq > old_freq) {
|
|
if (count > 1) {
|
|
ret = rockchip_opp_set_volt(dev, mem_reg, &new_supplies[1], "mem");
|
|
if (ret)
|
|
goto restore_voltage;
|
|
}
|
|
ret = rockchip_opp_set_volt(dev, vdd_reg, &new_supplies[0], "vdd");
|
|
if (ret)
|
|
goto restore_voltage;
|
|
rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active);
|
|
} else {
|
|
rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active);
|
|
ret = rockchip_opp_set_volt(dev, vdd_reg, &new_supplies[0], "vdd");
|
|
if (ret)
|
|
goto restore_voltage;
|
|
if (count > 1) {
|
|
ret = rockchip_opp_set_volt(dev, mem_reg, &new_supplies[1], "mem");
|
|
if (ret)
|
|
goto restore_voltage;
|
|
}
|
|
}
|
|
|
|
clk_bulk_disable_unprepare(info->nclocks, info->clocks);
|
|
|
|
return 0;
|
|
|
|
restore_voltage:
|
|
rockchip_get_read_margin(dev, info, old_supplies[0].u_volt, &target_rm);
|
|
rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active);
|
|
|
|
if (old_supplies[0].u_volt) {
|
|
if (count > 1 && old_supplies[1].u_volt) {
|
|
ret = rockchip_opp_set_volt(dev, mem_reg, &old_supplies[1], "mem");
|
|
if (ret)
|
|
goto dis_clks;
|
|
}
|
|
ret = rockchip_opp_set_volt(dev, vdd_reg, &old_supplies[0], "vdd");
|
|
if (ret)
|
|
goto dis_clks;
|
|
}
|
|
|
|
dis_clks:
|
|
clk_bulk_disable_unprepare(info->nclocks, info->clocks);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_opp_config_regulators);
|
|
|
|
int rockchip_opp_config_clks(struct device *dev, struct opp_table *opp_table,
|
|
struct dev_pm_opp *opp, void *data,
|
|
bool scaling_down, struct rockchip_opp_info *info)
|
|
{
|
|
unsigned long *target = data;
|
|
int ret;
|
|
|
|
if (info->is_scmi_clk && !info->is_runtime_active)
|
|
return 0;
|
|
|
|
ret = clk_bulk_prepare_enable(info->nclocks, info->clocks);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable opp clks\n");
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(dev, "%lu -> %lu (Hz)\n", opp_table->rate_clk_single, *target);
|
|
ret = clk_set_rate(opp_table->clk, *target);
|
|
if (ret)
|
|
dev_err(dev, "failed to set clock rate: %lu\n", *target);
|
|
else
|
|
opp_table->rate_clk_single = *target;
|
|
|
|
clk_bulk_disable_unprepare(info->nclocks, info->clocks);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_opp_config_clks);
|
|
|
|
int rockchip_opp_check_rate_volt(struct device *dev, struct rockchip_opp_info *info)
|
|
{
|
|
struct regulator *vdd_reg = NULL;
|
|
struct regulator *mem_reg = NULL;
|
|
struct dev_pm_opp *opp;
|
|
unsigned long old_rate = 0, new_rate = 0;
|
|
unsigned long new_volt = 0, new_volt_mem = 0;
|
|
int old_volt = 0, old_volt_mem = 0;
|
|
u32 target_rm = UINT_MAX;
|
|
bool is_set_clk = true;
|
|
bool is_set_rm = false;
|
|
int ret = 0;
|
|
|
|
if (!info->regulators || !info->clk)
|
|
return 0;
|
|
|
|
vdd_reg = info->regulators[0];
|
|
old_rate = clk_get_rate(info->clk);
|
|
old_volt = regulator_get_voltage(vdd_reg);
|
|
if (info->regulator_count > 1) {
|
|
mem_reg = info->regulators[1];
|
|
old_volt_mem = regulator_get_voltage(mem_reg);
|
|
}
|
|
|
|
if (info->init_freq) {
|
|
new_rate = info->init_freq * 1000;
|
|
info->init_freq = 0;
|
|
} else {
|
|
new_rate = old_rate;
|
|
}
|
|
opp = dev_pm_opp_find_freq_ceil(dev, &new_rate);
|
|
if (IS_ERR(opp)) {
|
|
opp = dev_pm_opp_find_freq_floor(dev, &new_rate);
|
|
if (IS_ERR(opp))
|
|
return PTR_ERR(opp);
|
|
}
|
|
new_volt = opp->supplies[0].u_volt;
|
|
if (info->regulator_count > 1)
|
|
new_volt_mem = opp->supplies[1].u_volt;
|
|
dev_pm_opp_put(opp);
|
|
|
|
if (old_rate == new_rate && info->is_rate_volt_checked) {
|
|
if (info->regulator_count > 1) {
|
|
if (old_volt == new_volt &&
|
|
new_volt_mem == old_volt_mem)
|
|
return 0;
|
|
} else if (old_volt == new_volt) {
|
|
return 0;
|
|
}
|
|
}
|
|
if (!new_volt || (info->regulator_count > 1 && !new_volt_mem))
|
|
return 0;
|
|
|
|
ret = clk_bulk_prepare_enable(info->nclocks, info->clocks);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable opp clks\n");
|
|
return ret;
|
|
}
|
|
|
|
if (info->is_scmi_clk && !info->is_runtime_active)
|
|
is_set_clk = false;
|
|
if (info->data && info->data->set_read_margin && info->is_runtime_active)
|
|
is_set_rm = true;
|
|
|
|
rockchip_get_read_margin(dev, info, new_volt, &target_rm);
|
|
|
|
dev_dbg(dev, "%s: %lu Hz --> %lu Hz, %lu %lu uV\n", __func__,
|
|
old_rate, new_rate, new_volt, new_volt_mem);
|
|
if (new_rate >= old_rate) {
|
|
if (old_volt > new_volt) {
|
|
ret = regulator_set_voltage(vdd_reg, new_volt, INT_MAX);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to set volt: %lu\n",
|
|
__func__, new_volt);
|
|
goto restore_voltage;
|
|
}
|
|
}
|
|
if (info->regulator_count > 1) {
|
|
ret = regulator_set_voltage(mem_reg, new_volt_mem,
|
|
INT_MAX);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to set volt: %lu\n",
|
|
__func__, new_volt_mem);
|
|
goto disable_clk;
|
|
}
|
|
}
|
|
if (old_volt <= new_volt) {
|
|
ret = regulator_set_voltage(vdd_reg, new_volt, INT_MAX);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to set volt: %lu\n",
|
|
__func__, new_volt);
|
|
goto restore_voltage;
|
|
}
|
|
}
|
|
rockchip_set_read_margin(dev, info, target_rm, is_set_rm);
|
|
if (new_rate == old_rate)
|
|
goto out;
|
|
}
|
|
|
|
if (is_set_clk) {
|
|
ret = clk_set_rate(info->clk, new_rate);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to set clock rate: %lu\n",
|
|
__func__, new_rate);
|
|
goto restore_rm;
|
|
}
|
|
}
|
|
|
|
if (new_rate < old_rate) {
|
|
rockchip_set_read_margin(dev, info, target_rm, is_set_rm);
|
|
ret = regulator_set_voltage(vdd_reg, new_volt,
|
|
INT_MAX);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to set volt: %lu\n",
|
|
__func__, new_volt);
|
|
goto restore_freq;
|
|
}
|
|
if (info->regulator_count > 1) {
|
|
ret = regulator_set_voltage(mem_reg, new_volt_mem,
|
|
INT_MAX);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to set volt: %lu\n",
|
|
__func__, new_volt_mem);
|
|
goto restore_freq;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
clk_bulk_disable_unprepare(info->nclocks, info->clocks);
|
|
|
|
return 0;
|
|
|
|
restore_freq:
|
|
if (is_set_clk && clk_set_rate(info->clk, old_rate))
|
|
dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
|
|
__func__, old_rate);
|
|
restore_rm:
|
|
rockchip_get_read_margin(dev, info, old_volt, &target_rm);
|
|
rockchip_set_read_margin(dev, info, target_rm, is_set_rm);
|
|
restore_voltage:
|
|
if (old_volt <= new_volt)
|
|
regulator_set_voltage(vdd_reg, old_volt, INT_MAX);
|
|
if (info->regulator_count > 1)
|
|
regulator_set_voltage(mem_reg, old_volt_mem, INT_MAX);
|
|
if (old_volt > new_volt)
|
|
regulator_set_voltage(vdd_reg, old_volt, INT_MAX);
|
|
disable_clk:
|
|
clk_bulk_disable_unprepare(info->nclocks, info->clocks);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_opp_check_rate_volt);
|
|
|
|
int rockchip_init_opp_table(struct device *dev, struct rockchip_opp_info *info,
|
|
char *clk_name, char *reg_name)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = rockchip_init_opp_info(dev, info, clk_name, reg_name);
|
|
if (ret) {
|
|
dev_err(dev, "failed to init opp info\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = dev_pm_opp_of_add_table(dev);
|
|
if (ret) {
|
|
dev_err(dev, "failed to add opp table\n");
|
|
rockchip_uninit_opp_info(dev, info);
|
|
return ret;
|
|
}
|
|
|
|
rockchip_adjust_opp_table(dev, info);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_init_opp_table);
|
|
|
|
void rockchip_uninit_opp_table(struct device *dev, struct rockchip_opp_info *info)
|
|
{
|
|
dev_pm_opp_of_remove_table(dev);
|
|
rockchip_uninit_opp_info(dev, info);
|
|
}
|
|
EXPORT_SYMBOL(rockchip_uninit_opp_table);
|
|
|
|
MODULE_DESCRIPTION("ROCKCHIP OPP Select");
|
|
MODULE_AUTHOR("Finley Xiao <finley.xiao@rock-chips.com>, Liang Chen <cl@rock-chips.com>");
|
|
MODULE_LICENSE("GPL");
|