diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig index 785f60c6f3ad1a09f517e69a69726a8178bed168..0a2bccab2992f3d3f7af4264fba65ac455848791 100644 --- a/drivers/soc/rockchip/Kconfig +++ b/drivers/soc/rockchip/Kconfig @@ -30,4 +30,18 @@ config ROCKCHIP_DTPM on this platform. That will create all the power capping capable devices. +config ROCKCHIP_OPP + tristate "Rockchip OPP select support" + depends on PM_DEVFREQ + help + Say y here to enable rockchip OPP support. + +config ROCKCHIP_PVTM + tristate "Rockchip PVTM support" + help + Say y here to enable pvtm support. + The Process-Voltage-Temperature Monitor (PVTM) is used to monitor + the chip performance variance caused by chip process, voltage and + temperature. + endif diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile index 23d414433c8c58557effc214337ec8e6ff17a461..c1340db95071743cc8cce3d8bbf326ae047704d7 100644 --- a/drivers/soc/rockchip/Makefile +++ b/drivers/soc/rockchip/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_ROCKCHIP_GRF) += grf.o obj-$(CONFIG_ROCKCHIP_IODOMAIN) += io-domain.o obj-$(CONFIG_ROCKCHIP_DTPM) += dtpm.o +obj-$(CONFIG_ROCKCHIP_OPP) += rockchip_opp_select.o +obj-$(CONFIG_ROCKCHIP_PVTM) += rockchip_pvtm.o diff --git a/drivers/soc/rockchip/rockchip_opp_select.c b/drivers/soc/rockchip/rockchip_opp_select.c new file mode 100644 index 0000000000000000000000000000000000000000..94c4a950c9be3ea57e1efecb797c5c335fd36e7e --- /dev/null +++ b/drivers/soc/rockchip/rockchip_opp_select.c @@ -0,0 +1,1775 @@ +/* + * 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/slab.h> +#include <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 regmap *grf; +}; + +struct lkg_conversion_table { + int temp; + int conv; +}; + +#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; + +/* + * 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; + pvtm->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(pvtm->grf)) + 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, old_volt); +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); + +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); +} +EXPORT_SYMBOL(rockchip_of_get_lkg_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->grf, info->pvtpll_avg_offset, &rate0); + /* max delay 2ms */ + for (i = 0; i < 20; i++) { + udelay(100); + regmap_read(info->grf, 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 (!opp_table) { + kfree(info->opp_table); + info->opp_table = NULL; + return -ENOMEM; + } + + 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; +} + +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->grf) + 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 (!opp_table) + 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: + dev_pm_opp_put_opp_table(opp_table); +} +EXPORT_SYMBOL(rockchip_pvtpll_calibrate_opp); + +static int rockchip_get_pvtm_pvtpll(struct device *dev, struct device_node *np, + 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; + + 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, pvtm->volt); + 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(pvtm->grf, 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, old_volt); +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, + 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; +} + +void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np, + char *reg_name, int process, + int *volt_sel, int *scale_sel) +{ + struct property *prop = NULL; + char name[NAME_MAX]; + int pvtm, ret; + + if (of_property_read_bool(np, "rockchip,pvtm-pvtpll")) + pvtm = rockchip_get_pvtm_pvtpll(dev, np, reg_name); + else + pvtm = rockchip_get_pvtm(dev, np, reg_name); + if (pvtm <= 0) + return; + + if (!volt_sel) + goto next; + if (process >= 0) { + snprintf(name, sizeof(name), + "rockchip,p%d-pvtm-voltage-sel", process); + 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; + if (process >= 0) { + snprintf(name, sizeof(name), + "rockchip,p%d-pvtm-scaling-sel", 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); +} +EXPORT_SYMBOL(rockchip_of_get_pvtm_sel); + +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); +} +EXPORT_SYMBOL(rockchip_of_get_bin_sel); + +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); +} +EXPORT_SYMBOL(rockchip_of_get_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); + +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; +} +EXPORT_SYMBOL(rockchip_get_volt_rm_table); + +void rockchip_get_scale_volt_sel(struct device *dev, char *lkg_name, + char *reg_name, int bin, int process, + int *scale, int *volt_sel) +{ + 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, process, + &lkg_volt_sel, &lkg_scale); + rockchip_of_get_pvtm_sel(dev, np, reg_name, process, + &pvtm_volt_sel, &pvtm_scale); + rockchip_of_get_bin_sel(dev, np, bin, &bin_scale); + rockchip_of_get_bin_volt_sel(dev, np, bin, &bin_volt_sel); + if (scale) + *scale = max3(lkg_scale, pvtm_scale, bin_scale); + if (volt_sel) { + if (bin_volt_sel >= 0) + *volt_sel = bin_volt_sel; + else + *volt_sel = max(lkg_volt_sel, pvtm_volt_sel); + } + + of_node_put(np); +} +EXPORT_SYMBOL(rockchip_get_scale_volt_sel); + +int rockchip_set_opp_prop_name(struct device *dev, int process, + int volt_sel) +{ + char name[MAX_PROP_NAME_LEN]; + + if (process >= 0) { + if (volt_sel >= 0) + snprintf(name, MAX_PROP_NAME_LEN, "P%d-L%d", + process, volt_sel); + else + snprintf(name, MAX_PROP_NAME_LEN, "P%d", process); + } else if (volt_sel >= 0) { + snprintf(name, MAX_PROP_NAME_LEN, "L%d", volt_sel); + } else { + return -1; + } + + return dev_pm_opp_set_prop_name(dev, name); +} +EXPORT_SYMBOL(rockchip_set_opp_prop_name); + +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 (!opp_table) { + ret = -ENOMEM; + 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 (!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 int rockchip_adjust_opp_table(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; +} + +int rockchip_adjust_power_scale(struct device *dev, int scale) +{ + 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_mbist_vmin(dev, np); + rockchip_adjust_opp_by_irdrop(dev, np, &safe_rate, &max_rate); + + dev_info(dev, "avs=%d\n", avs); + 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_table(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); + if (max_rate) + opp_scale = rockchip_pll_clk_rate_to_scale(clk, max_rate); + target_scale = max(irdrop_scale, scale); + if (target_scale <= 0) + goto out_clk; + dev_dbg(dev, "target_scale=%d, irdrop_scale=%d, scale=%d\n", + target_scale, irdrop_scale, scale); + + 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_table(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_table(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; +} +EXPORT_SYMBOL(rockchip_adjust_power_scale); + +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); + +int rockchip_init_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + 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; +} +EXPORT_SYMBOL(rockchip_init_read_margin); + +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_init_opp_table(struct device *dev, struct rockchip_opp_info *info, + char *lkg_name, char *reg_name) +{ + struct device_node *np; + int bin = -EINVAL, process = -EINVAL; + int scale = 0, volt_sel = -EINVAL; + int ret = 0, num_clks = 0, i; + 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) + goto next; + info->dev = dev; + + num_clks = of_clk_get_parent_count(np); + if (num_clks > 0) { + info->clks = devm_kcalloc(dev, num_clks, sizeof(*info->clks), + GFP_KERNEL); + if (!info->clks) { + ret = -ENOMEM; + goto out; + } + for (i = 0; i < num_clks; i++) { + info->clks[i].clk = of_clk_get(np, i); + if (IS_ERR(info->clks[i].clk)) { + ret = PTR_ERR(info->clks[i].clk); + dev_err(dev, "%s: failed to get clk %d\n", + np->name, i); + goto out; + } + } + info->num_clks = num_clks; + ret = clk_bulk_prepare_enable(info->num_clks, info->clks); + 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->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(info->grf)) + info->grf = NULL; + 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, &bin, &process); + +next: + rockchip_get_scale_volt_sel(dev, lkg_name, reg_name, bin, process, + &scale, &volt_sel); + rockchip_set_opp_prop_name(dev, process, volt_sel); + ret = dev_pm_opp_of_add_table(dev); + if (ret) { + dev_err(dev, "Invalid operating-points in device tree.\n"); + goto dis_opp_clk; + } + rockchip_adjust_power_scale(dev, scale); + rockchip_pvtpll_calibrate_opp(info); + +dis_opp_clk: + if (info && info->clks) + clk_bulk_disable_unprepare(info->num_clks, info->clks); +out: + of_node_put(np); + + return ret; +} +EXPORT_SYMBOL(rockchip_init_opp_table); + +int rockchip_opp_dump_cur_state(struct device *dev) +{ + struct clk *clk; + struct opp_table *opp_table; + int volt_vdd, volt_mem; + + if (!dev) + return -ENODEV; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) { + dev_err(dev, "%s: device opp doesn't exist\n", __func__); + return PTR_ERR(opp_table); + } + + clk = opp_table->clk; + if (IS_ERR(clk)) { + dev_err(dev, "%s: No clock available for the device\n", + __func__); + dev_pm_opp_put_opp_table(opp_table); + return PTR_ERR(clk); + } + + if (opp_table->regulator_count == 1) { + volt_vdd = regulator_get_voltage(opp_table->regulators[0]); + dev_info(dev, "cur_freq: %lu Hz, volt: %d uV\n", + clk_get_rate(clk), volt_vdd); + } + + if (opp_table->regulator_count == 2) { + volt_vdd = regulator_get_voltage(opp_table->regulators[0]); + volt_mem = regulator_get_voltage(opp_table->regulators[1]); + dev_info(dev, "cur_freq: %lu Hz, volt_vdd: %d uV, volt_mem: %d uV\n", + clk_get_rate(clk), volt_vdd, volt_mem); + } + + dev_pm_opp_put_opp_table(opp_table); + + return 0; +} +EXPORT_SYMBOL(rockchip_opp_dump_cur_state); + +MODULE_DESCRIPTION("ROCKCHIP OPP Select"); +MODULE_AUTHOR("Finley Xiao <finley.xiao@rock-chips.com>, Liang Chen <cl@rock-chips.com>"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/drivers/soc/rockchip/rockchip_pvtm.c b/drivers/soc/rockchip/rockchip_pvtm.c new file mode 100644 index 0000000000000000000000000000000000000000..314f997e83b3c6fa82009bfb6126f3da711e7bb3 --- /dev/null +++ b/drivers/soc/rockchip/rockchip_pvtm.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Rockchip PVTM support. + * + * Copyright (c) 2016 Rockchip Electronics Co. Ltd. + * Author: Finley Xiao <finley.xiao@rock-chips.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_clk.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <soc/rockchip/pvtm.h> +#include <linux/thermal.h> + +#define wr_mask_bit(v, off, mask) ((v) << (off) | (mask) << (16 + off)) + +#define PVTM(_id, _name, _num_rings, _start, _en, _cal, _done, _freq) \ +{ \ + .id = _id, \ + .name = _name, \ + .num_rings = _num_rings, \ + .bit_start = _start, \ + .bit_en = _en, \ + .reg_cal = _cal, \ + .bit_freq_done = _done, \ + .reg_freq = _freq, \ +} + +struct rockchip_pvtm; + +struct rockchip_pvtm_ops { + u32 (*get_value)(struct rockchip_pvtm *pvtm, unsigned int ring_sel, + unsigned int time_us); + void (*set_ring_sel)(struct rockchip_pvtm *pvtm, unsigned int ring_sel); +}; + +struct rockchip_pvtm_info { + u32 reg_cal; + u32 reg_freq; + unsigned char id; + unsigned char *name; + unsigned int num_rings; + unsigned int bit_start; + unsigned int bit_en; + unsigned int bit_freq_done; +}; + +struct rockchip_pvtm_data { + u32 con; + u32 sta; + unsigned int num_pvtms; + const struct rockchip_pvtm_info *infos; + const struct rockchip_pvtm_ops ops; +}; + +struct rockchip_pvtm { + u32 con; + u32 sta; + struct list_head node; + struct device *dev; + struct regmap *grf; + void __iomem *base; + int num_clks; + struct clk_bulk_data *clks; + struct reset_control *rst; + struct thermal_zone_device *tz; + const struct rockchip_pvtm_info *info; + const struct rockchip_pvtm_ops *ops; + struct dentry *dentry; +}; + +static LIST_HEAD(pvtm_list); + +#ifdef CONFIG_DEBUG_FS +static struct dentry *rockchip_pvtm_debugfs_root; + +static int pvtm_value_show(struct seq_file *s, void *data) +{ + struct rockchip_pvtm *pvtm = (struct rockchip_pvtm *)s->private; + u32 value; + int i, ret, cur_temp; + + if (!pvtm || !pvtm->ops->get_value) { + seq_puts(s, "unsupported\n"); + return 0; + } + + if (pvtm->tz && pvtm->tz->ops && pvtm->tz->ops->get_temp) { + ret = pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); + if (ret) + dev_err(pvtm->dev, "debug failed to get temp\n"); + else + seq_printf(s, "temp: %d ", cur_temp); + } + seq_puts(s, "pvtm: "); + for (i = 0; i < pvtm->info->num_rings; i++) { + value = pvtm->ops->get_value(pvtm, i, 1000); + seq_printf(s, "%d ", value); + } + seq_puts(s, "\n"); + + return 0; +} + +static int pvtm_value_open(struct inode *inode, struct file *file) +{ + return single_open(file, pvtm_value_show, inode->i_private); +} + +static const struct file_operations pvtm_value_fops = { + .open = pvtm_value_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int rockchip_pvtm_debugfs_init(void) +{ + rockchip_pvtm_debugfs_root = debugfs_create_dir("pvtm", NULL); + if (IS_ERR_OR_NULL(rockchip_pvtm_debugfs_root)) { + pr_err("Failed to create pvtm debug directory\n"); + rockchip_pvtm_debugfs_root = NULL; + return -ENOMEM; + } + + return 0; +} + +static void rockchip_pvtm_debugfs_exit(void) +{ + debugfs_remove_recursive(rockchip_pvtm_debugfs_root); +} + +static int rockchip_pvtm_add_debugfs(struct rockchip_pvtm *pvtm) +{ + struct dentry *d; + + if (!rockchip_pvtm_debugfs_root) + return 0; + + pvtm->dentry = debugfs_create_dir(pvtm->info->name, + rockchip_pvtm_debugfs_root); + if (!pvtm->dentry) { + dev_err(pvtm->dev, "failed to create pvtm %s debug dir\n", + pvtm->info->name); + return -ENOMEM; + } + + d = debugfs_create_file("value", 0444, pvtm->dentry, + (void *)pvtm, &pvtm_value_fops); + if (!d) { + dev_err(pvtm->dev, "failed to pvtm %s value node\n", + pvtm->info->name); + debugfs_remove_recursive(pvtm->dentry); + return -ENOMEM; + } + + return 0; +} +#else +static inline int rockchip_pvtm_debugfs_init(void) +{ + return 0; +} + +static inline void rockchip_pvtm_debugfs_exit(void) +{ +} + +static inline int rockchip_pvtm_add_debugfs(struct rockchip_pvtm *pvtm) +{ + return 0; +} +#endif + +static int rockchip_pvtm_reset(struct rockchip_pvtm *pvtm) +{ + int ret; + + ret = reset_control_assert(pvtm->rst); + if (ret) { + dev_err(pvtm->dev, "failed to assert pvtm %d\n", ret); + return ret; + } + + udelay(2); + + ret = reset_control_deassert(pvtm->rst); + if (ret) { + dev_err(pvtm->dev, "failed to deassert pvtm %d\n", ret); + return ret; + } + + return 0; +} + +u32 rockchip_get_pvtm_value(unsigned int id, unsigned int ring_sel, + unsigned int time_us) +{ + struct rockchip_pvtm *p, *pvtm = NULL; + + if (list_empty(&pvtm_list)) { + pr_err("pvtm list NULL\n"); + return -EINVAL; + } + + list_for_each_entry(p, &pvtm_list, node) { + if (p->info->id == id) { + pvtm = p; + break; + } + } + + if (!pvtm) { + pr_err("invalid pvtm id %d\n", id); + return -EINVAL; + } + + if (ring_sel >= pvtm->info->num_rings) { + pr_err("invalid pvtm ring %d\n", ring_sel); + return -EINVAL; + } + + return pvtm->ops->get_value(pvtm, ring_sel, time_us); +} +EXPORT_SYMBOL(rockchip_get_pvtm_value); + +static void rockchip_pvtm_delay(unsigned int delay) +{ + unsigned int ms = delay / 1000; + unsigned int us = delay % 1000; + + if (ms > 0) { + if (ms < 20) + us += ms * 1000; + else + msleep(ms); + } + + if (us >= 10) + usleep_range(us, us + 100); + else + udelay(us); +} + +static void px30_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + unsigned int id = pvtm->info->id; + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(ring_sel, (id * 0x4 + 0x2), 0x3)); +} + +static void rk1808_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(ring_sel, 0x2, 0x7)); +} + +static void rk3399_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + unsigned int id = pvtm->info->id; + + if (id == 1) { + regmap_write(pvtm->grf, pvtm->con + 0x14, + wr_mask_bit(ring_sel >> 0x3, 0, 0x1)); + ring_sel &= 0x3; + } + if (id != 4) + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(ring_sel, (id * 0x4 + 0x2), 0x3)); +} + +static u32 rockchip_pvtm_get_value(struct rockchip_pvtm *pvtm, + unsigned int ring_sel, + unsigned int time_us) +{ + const struct rockchip_pvtm_info *info = pvtm->info; + unsigned int clk_cnt, check_cnt = 100; + u32 sta, val = 0; + int ret; + + ret = clk_bulk_prepare_enable(pvtm->num_clks, pvtm->clks); + if (ret < 0) { + dev_err(pvtm->dev, "failed to prepare/enable pvtm clks\n"); + return 0; + } + ret = rockchip_pvtm_reset(pvtm); + if (ret) { + dev_err(pvtm->dev, "failed to reset pvtm\n"); + goto disable_clks; + } + + /* if last status is enabled, stop calculating cycles first*/ + regmap_read(pvtm->grf, pvtm->con, &sta); + if (sta & BIT(info->bit_en)) + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0, info->bit_start, 0x1)); + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0x1, info->bit_en, 0x1)); + + if (pvtm->ops->set_ring_sel) + pvtm->ops->set_ring_sel(pvtm, ring_sel); + + /* clk = 24 Mhz, T = 1 / 24 us */ + clk_cnt = time_us * 24; + regmap_write(pvtm->grf, pvtm->con + info->reg_cal, clk_cnt); + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0x1, info->bit_start, 0x1)); + + rockchip_pvtm_delay(time_us); + + while (check_cnt) { + regmap_read(pvtm->grf, pvtm->sta, &sta); + if (sta & BIT(info->bit_freq_done)) + break; + udelay(4); + check_cnt--; + } + + if (check_cnt) { + regmap_read(pvtm->grf, pvtm->sta + info->reg_freq, &val); + } else { + dev_err(pvtm->dev, "wait pvtm_done timeout!\n"); + val = 0; + } + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0, info->bit_start, 0x1)); + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0, info->bit_en, 0x1)); + +disable_clks: + clk_bulk_disable_unprepare(pvtm->num_clks, pvtm->clks); + + return val; +} + +static void rv1106_core_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + writel_relaxed(wr_mask_bit(ring_sel + 4, 0x2, 0x7), pvtm->base + pvtm->con); +} + +static void rv1126_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + writel_relaxed(wr_mask_bit(ring_sel, 0x2, 0x7), pvtm->base + pvtm->con); +} + +static u32 rv1126_pvtm_get_value(struct rockchip_pvtm *pvtm, + unsigned int ring_sel, + unsigned int time_us) +{ + const struct rockchip_pvtm_info *info = pvtm->info; + unsigned int clk_cnt, check_cnt = 100; + u32 sta, val = 0; + int ret; + + ret = clk_bulk_prepare_enable(pvtm->num_clks, pvtm->clks); + if (ret < 0) { + dev_err(pvtm->dev, "failed to prepare/enable pvtm clks\n"); + return 0; + } + ret = rockchip_pvtm_reset(pvtm); + if (ret) { + dev_err(pvtm->dev, "failed to reset pvtm\n"); + goto disable_clks; + } + + /* if last status is enabled, stop calculating cycles first*/ + sta = readl_relaxed(pvtm->base + pvtm->con); + if (sta & BIT(info->bit_en)) + writel_relaxed(wr_mask_bit(0, info->bit_start, 0x1), + pvtm->base + pvtm->con); + + writel_relaxed(wr_mask_bit(0x1, info->bit_en, 0x1), + pvtm->base + pvtm->con); + + if (pvtm->ops->set_ring_sel) + pvtm->ops->set_ring_sel(pvtm, ring_sel); + + /* clk = 24 Mhz, T = 1 / 24 us */ + clk_cnt = time_us * 24; + writel_relaxed(clk_cnt, pvtm->base + pvtm->con + info->reg_cal); + + writel_relaxed(wr_mask_bit(0x1, info->bit_start, 0x1), + pvtm->base + pvtm->con); + + rockchip_pvtm_delay(time_us); + + while (check_cnt) { + sta = readl_relaxed(pvtm->base + pvtm->sta); + if (sta & BIT(info->bit_freq_done)) + break; + udelay(4); + check_cnt--; + } + + if (check_cnt) { + val = readl_relaxed(pvtm->base + pvtm->sta + info->reg_freq); + } else { + dev_err(pvtm->dev, "wait pvtm_done timeout!\n"); + val = 0; + } + + writel_relaxed(wr_mask_bit(0, info->bit_start, 0x1), + pvtm->base + pvtm->con); + writel_relaxed(wr_mask_bit(0, info->bit_en, 0x1), + pvtm->base + pvtm->con); + +disable_clks: + clk_bulk_disable_unprepare(pvtm->num_clks, pvtm->clks); + + return val; +} + +static const struct rockchip_pvtm_info px30_pvtm_infos[] = { + PVTM(0, "core", 3, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data px30_pvtm = { + .con = 0x80, + .sta = 0x88, + .num_pvtms = ARRAY_SIZE(px30_pvtm_infos), + .infos = px30_pvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + .set_ring_sel = px30_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info px30_pmupvtm_infos[] = { + PVTM(1, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data px30_pmupvtm = { + .con = 0x180, + .sta = 0x190, + .num_pvtms = ARRAY_SIZE(px30_pmupvtm_infos), + .infos = px30_pmupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rk1808_pvtm_infos[] = { + PVTM(0, "core", 5, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk1808_pvtm = { + .con = 0x80, + .sta = 0x88, + .num_pvtms = ARRAY_SIZE(rk1808_pvtm_infos), + .infos = rk1808_pvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + .set_ring_sel = rk1808_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk1808_pmupvtm_infos[] = { + PVTM(1, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk1808_pmupvtm = { + .con = 0x180, + .sta = 0x190, + .num_pvtms = ARRAY_SIZE(rk1808_pmupvtm_infos), + .infos = rk1808_pmupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rk1808_npupvtm_infos[] = { + PVTM(2, "npu", 5, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk1808_npupvtm = { + .con = 0x780, + .sta = 0x788, + .num_pvtms = ARRAY_SIZE(rk1808_npupvtm_infos), + .infos = rk1808_npupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + .set_ring_sel = rk1808_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3288_pvtm_infos[] = { + PVTM(0, "core", 1, 0, 1, 0x4, 1, 0x4), + PVTM(1, "gpu", 1, 8, 9, 0x8, 0, 0x8), +}; + +static const struct rockchip_pvtm_data rk3288_pvtm = { + .con = 0x368, + .sta = 0x374, + .num_pvtms = ARRAY_SIZE(rk3288_pvtm_infos), + .infos = rk3288_pvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_data rk3308_pmupvtm = { + .con = 0x440, + .sta = 0x448, + .num_pvtms = ARRAY_SIZE(px30_pmupvtm_infos), + .infos = px30_pmupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rk3399_pvtm_infos[] = { + PVTM(0, "core_l", 4, 0, 1, 0x4, 0, 0x4), + PVTM(1, "core_b", 6, 4, 5, 0x8, 1, 0x8), + PVTM(2, "ddr", 4, 8, 9, 0xc, 3, 0x10), + PVTM(3, "gpu", 4, 12, 13, 0x10, 2, 0xc), +}; + +static const struct rockchip_pvtm_data rk3399_pvtm = { + .con = 0xe600, + .sta = 0xe620, + .num_pvtms = ARRAY_SIZE(rk3399_pvtm_infos), + .infos = rk3399_pvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + .set_ring_sel = rk3399_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3399_pmupvtm_infos[] = { + PVTM(4, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3399_pmupvtm = { + .con = 0x240, + .sta = 0x248, + .num_pvtms = ARRAY_SIZE(rk3399_pmupvtm_infos), + .infos = rk3399_pmupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rk3568_corepvtm_infos[] = { + PVTM(0, "core", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3568_corepvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3568_corepvtm_infos), + .infos = rk3568_corepvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3568_gpupvtm_infos[] = { + PVTM(1, "gpu", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3568_gpupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3568_gpupvtm_infos), + .infos = rk3568_gpupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3568_npupvtm_infos[] = { + PVTM(2, "npu", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3568_npupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3568_npupvtm_infos), + .infos = rk3568_npupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_bigcore0_pvtm_infos[] = { + PVTM(0, "bigcore0", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_bigcore0_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_bigcore0_pvtm_infos), + .infos = rk3588_bigcore0_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_bigcore1_pvtm_infos[] = { + PVTM(1, "bigcore1", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_bigcore1_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_bigcore1_pvtm_infos), + .infos = rk3588_bigcore1_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_litcore_pvtm_infos[] = { + PVTM(2, "litcore", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_litcore_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_litcore_pvtm_infos), + .infos = rk3588_litcore_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_npu_pvtm_infos[] = { + PVTM(3, "npu", 2, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_npu_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_npu_pvtm_infos), + .infos = rk3588_npu_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_gpu_pvtm_infos[] = { + PVTM(4, "gpu", 2, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_gpu_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_gpu_pvtm_infos), + .infos = rk3588_gpu_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_pmu_pvtm_infos[] = { + PVTM(5, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_pmu_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_pmu_pvtm_infos), + .infos = rk3588_pmu_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rv1106_corepvtm_infos[] = { + PVTM(0, "core", 2, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1106_corepvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1106_corepvtm_infos), + .infos = rv1106_corepvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1106_core_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rv1106_pmupvtm_infos[] = { + PVTM(1, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1106_pmupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1106_pmupvtm_infos), + .infos = rv1106_pmupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rv1126_cpupvtm_infos[] = { + PVTM(0, "cpu", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1126_cpupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1126_cpupvtm_infos), + .infos = rv1126_cpupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rv1126_npupvtm_infos[] = { + PVTM(1, "npu", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1126_npupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1126_npupvtm_infos), + .infos = rv1126_npupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rv1126_pmupvtm_infos[] = { + PVTM(2, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1126_pmupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1126_pmupvtm_infos), + .infos = rv1126_pmupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + }, +}; + +static const struct of_device_id rockchip_pvtm_match[] = { +#ifdef CONFIG_CPU_PX30 + { + .compatible = "rockchip,px30-pvtm", + .data = (void *)&px30_pvtm, + }, + { + .compatible = "rockchip,px30-pmu-pvtm", + .data = (void *)&px30_pmupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK1808 + { + .compatible = "rockchip,rk1808-pvtm", + .data = (void *)&rk1808_pvtm, + }, + { + .compatible = "rockchip,rk1808-pmu-pvtm", + .data = (void *)&rk1808_pmupvtm, + }, + { + .compatible = "rockchip,rk1808-npu-pvtm", + .data = (void *)&rk1808_npupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3288 + { + .compatible = "rockchip,rk3288-pvtm", + .data = (void *)&rk3288_pvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3308 + { + .compatible = "rockchip,rk3308-pvtm", + .data = (void *)&px30_pvtm, + }, + { + .compatible = "rockchip,rk3308-pmu-pvtm", + .data = (void *)&rk3308_pmupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3399 + { + .compatible = "rockchip,rk3399-pvtm", + .data = (void *)&rk3399_pvtm, + }, + { + .compatible = "rockchip,rk3399-pmu-pvtm", + .data = (void *)&rk3399_pmupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3568 + { + .compatible = "rockchip,rK3568-core-pvtm", + .data = (void *)&rk3568_corepvtm, + }, + { + .compatible = "rockchip,rk3568-gpu-pvtm", + .data = (void *)&rk3568_gpupvtm, + }, + { + .compatible = "rockchip,rk3568-npu-pvtm", + .data = (void *)&rk3568_npupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3588 + { + .compatible = "rockchip,rk3588-bigcore0-pvtm", + .data = (void *)&rk3588_bigcore0_pvtm, + }, + { + .compatible = "rockchip,rk3588-bigcore1-pvtm", + .data = (void *)&rk3588_bigcore1_pvtm, + }, + { + .compatible = "rockchip,rk3588-litcore-pvtm", + .data = (void *)&rk3588_litcore_pvtm, + }, + { + .compatible = "rockchip,rk3588-gpu-pvtm", + .data = (void *)&rk3588_gpu_pvtm, + }, + { + .compatible = "rockchip,rk3588-npu-pvtm", + .data = (void *)&rk3588_npu_pvtm, + }, + { + .compatible = "rockchip,rk3588-pmu-pvtm", + .data = (void *)&rk3588_pmu_pvtm, + }, +#endif +#ifdef CONFIG_CPU_RV1106 + { + .compatible = "rockchip,rv1106-core-pvtm", + .data = (void *)&rv1106_corepvtm, + }, + { + .compatible = "rockchip,rv1106-pmu-pvtm", + .data = (void *)&rv1106_pmupvtm, + }, +#endif +#ifdef CONFIG_CPU_RV1126 + { + .compatible = "rockchip,rv1126-cpu-pvtm", + .data = (void *)&rv1126_cpupvtm, + }, + { + .compatible = "rockchip,rv1126-npu-pvtm", + .data = (void *)&rv1126_npupvtm, + }, + { + .compatible = "rockchip,rv1126-pmu-pvtm", + .data = (void *)&rv1126_pmupvtm, + }, +#endif + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rockchip_pvtm_match); + +static int rockchip_pvtm_get_index(const struct rockchip_pvtm_data *data, + u32 ch, u32 *index) +{ + int i; + + for (i = 0; i < data->num_pvtms; i++) { + if (ch == data->infos[i].id) { + *index = i; + return 0; + } + } + + return -EINVAL; +} + +static struct rockchip_pvtm * +rockchip_pvtm_init(struct device *dev, struct device_node *node, + const struct rockchip_pvtm_data *data, + struct regmap *grf, void __iomem *base) +{ + struct rockchip_pvtm *pvtm; + const char *tz_name; + u32 id, index; + int i; + + if (of_property_read_u32(node, "reg", &id)) { + dev_err(dev, "%s: failed to retrieve pvtm id\n", node->name); + return NULL; + } + if (rockchip_pvtm_get_index(data, id, &index)) { + dev_err(dev, "%s: invalid pvtm id %d\n", node->name, id); + return NULL; + } + + pvtm = devm_kzalloc(dev, sizeof(*pvtm), GFP_KERNEL); + if (!pvtm) + return NULL; + + pvtm->dev = dev; + pvtm->grf = grf; + pvtm->base = base; + pvtm->con = data->con; + pvtm->sta = data->sta; + pvtm->ops = &data->ops; + pvtm->info = &data->infos[index]; + + if (!of_property_read_string(node, "thermal-zone", &tz_name)) { + pvtm->tz = thermal_zone_get_zone_by_name(tz_name); + if (IS_ERR(pvtm->tz)) { + dev_err(pvtm->dev, "failed to retrieve pvtm_tz\n"); + pvtm->tz = NULL; + } + } + + pvtm->num_clks = of_clk_get_parent_count(node); + if (pvtm->num_clks <= 0) { + dev_err(dev, "%s: does not have clocks\n", node->name); + goto clk_num_err; + } + pvtm->clks = devm_kcalloc(dev, pvtm->num_clks, sizeof(*pvtm->clks), + GFP_KERNEL); + if (!pvtm->clks) + goto clk_num_err; + for (i = 0; i < pvtm->num_clks; i++) { + pvtm->clks[i].clk = of_clk_get(node, i); + if (IS_ERR(pvtm->clks[i].clk)) { + dev_err(dev, "%s: failed to get clk at index %d\n", + node->name, i); + goto clk_err; + } + } + + pvtm->rst = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(pvtm->rst)) + dev_dbg(dev, "%s: failed to get reset\n", node->name); + + rockchip_pvtm_add_debugfs(pvtm); + + return pvtm; + +clk_err: + while (--i >= 0) + clk_put(pvtm->clks[i].clk); + devm_kfree(dev, pvtm->clks); +clk_num_err: + devm_kfree(dev, pvtm); + + return NULL; +} + +static int rockchip_pvtm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct device_node *node; + const struct of_device_id *match; + struct rockchip_pvtm *pvtm; + struct regmap *grf = NULL; + void __iomem *base = NULL; + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) { + dev_err(dev, "missing pvtm data\n"); + return -EINVAL; + } + + if (dev->parent && dev->parent->of_node) { + grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(grf)) + return PTR_ERR(grf); + } else { + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + } + + for_each_available_child_of_node(np, node) { + pvtm = rockchip_pvtm_init(dev, node, match->data, grf, base); + if (!pvtm) { + dev_err(dev, "failed to handle node %s\n", + node->full_name); + continue; + } + list_add(&pvtm->node, &pvtm_list); + dev_info(dev, "%s probed\n", node->full_name); + } + + return 0; +} + +static struct platform_driver rockchip_pvtm_driver = { + .probe = rockchip_pvtm_probe, + .driver = { + .name = "rockchip-pvtm", + .of_match_table = rockchip_pvtm_match, + }, +}; + +static int __init rockchip_pvtm_module_init(void) +{ + rockchip_pvtm_debugfs_init(); + + return platform_driver_register(&rockchip_pvtm_driver); +} +module_init(rockchip_pvtm_module_init); + +static void __exit rockchip_pvtm_module_exit(void) +{ + rockchip_pvtm_debugfs_exit(); + platform_driver_unregister(&rockchip_pvtm_driver); +} +module_exit(rockchip_pvtm_module_exit); + +MODULE_DESCRIPTION("Rockchip PVTM driver"); +MODULE_AUTHOR("Finley Xiao <finley.xiao@rock-chips.com>"); +MODULE_LICENSE("GPL v2"); \ No newline at end of file diff --git a/include/soc/rockchip/pvtm.h b/include/soc/rockchip/pvtm.h new file mode 100644 index 0000000000000000000000000000000000000000..3d2495cfd6e5ad6bcf27548258f418f903d3af77 --- /dev/null +++ b/include/soc/rockchip/pvtm.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOC_ROCKCHIP_PVTM_H +#define __SOC_ROCKCHIP_PVTM_H + +#if IS_ENABLED(CONFIG_ROCKCHIP_PVTM) +u32 rockchip_get_pvtm_value(unsigned int id, unsigned int ring_sel, + unsigned int time_us); +#else +static inline u32 rockchip_get_pvtm_value(unsigned int id, + unsigned int ring_sel, + unsigned int time_us) +{ + return 0; +} +#endif + +#endif /* __SOC_ROCKCHIP_PVTM_H */ diff --git a/include/soc/rockchip/rockchip_opp_select.h b/include/soc/rockchip/rockchip_opp_select.h new file mode 100644 index 0000000000000000000000000000000000000000..2afac7cc73cf6cdea132b488bb774da1d2267395 --- /dev/null +++ b/include/soc/rockchip/rockchip_opp_select.h @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef __SOC_ROCKCHIP_OPP_SELECT_H +#define __SOC_ROCKCHIP_OPP_SELECT_H + +#define VOLT_RM_TABLE_END ~1 + +#define OPP_INTERMEDIATE_MASK 0x3f +#define OPP_INTERMEDIATE_RATE BIT(0) +#define OPP_SCALING_UP_RATE BIT(1) +#define OPP_SCALING_UP_INTER (OPP_INTERMEDIATE_RATE | OPP_SCALING_UP_RATE) +#define OPP_SCALING_DOWN_INTER OPP_INTERMEDIATE_RATE +#define OPP_LENGTH_LOW BIT(2) + +struct rockchip_opp_info; + +struct volt_rm_table { + int volt; + int rm; +}; + +struct rockchip_opp_data { + int (*get_soc_info)(struct device *dev, struct device_node *np, + int *bin, int *process); + int (*set_soc_info)(struct device *dev, struct device_node *np, + int bin, int process, int volt_sel); + int (*set_read_margin)(struct device *dev, + struct rockchip_opp_info *opp_info, + u32 rm); +}; + +struct pvtpll_opp_table { + unsigned long rate; + unsigned long u_volt; + unsigned long u_volt_min; + unsigned long u_volt_max; + unsigned long u_volt_mem; + unsigned long u_volt_mem_min; + unsigned long u_volt_mem_max; +}; + +struct rockchip_opp_info { + struct device *dev; + struct pvtpll_opp_table *opp_table; + const struct rockchip_opp_data *data; + struct volt_rm_table *volt_rm_tbl; + struct regmap *grf; + struct regmap *dsu_grf; + struct clk_bulk_data *clks; + struct clk *scmi_clk; + /* The threshold frequency for set intermediate rate */ + unsigned long intermediate_threshold_freq; + unsigned int pvtpll_avg_offset; + unsigned int pvtpll_min_rate; + unsigned int pvtpll_volt_step; + int num_clks; + /* The read margin for low voltage */ + u32 low_rm; + u32 current_rm; + u32 target_rm; +}; + +#if IS_ENABLED(CONFIG_ROCKCHIP_OPP) +int rockchip_of_get_leakage(struct device *dev, char *lkg_name, int *leakage); +void rockchip_of_get_lkg_sel(struct device *dev, struct device_node *np, + char *lkg_name, int process, + int *volt_sel, int *scale_sel); +void rockchip_pvtpll_calibrate_opp(struct rockchip_opp_info *info); +void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np, + char *reg_name, int process, + int *volt_sel, int *scale_sel); +void rockchip_of_get_bin_sel(struct device *dev, struct device_node *np, + int bin, int *scale_sel); +void rockchip_of_get_bin_volt_sel(struct device *dev, struct device_node *np, + int bin, int *bin_volt_sel); +int rockchip_nvmem_cell_read_u8(struct device_node *np, const char *cell_id, + u8 *val); +int rockchip_nvmem_cell_read_u16(struct device_node *np, const char *cell_id, + u16 *val); +int rockchip_get_volt_rm_table(struct device *dev, struct device_node *np, + char *porp_name, struct volt_rm_table **table); +void rockchip_get_opp_data(const struct of_device_id *matches, + struct rockchip_opp_info *info); +void rockchip_get_scale_volt_sel(struct device *dev, char *lkg_name, + char *reg_name, int bin, int process, + int *scale, int *volt_sel); +int rockchip_set_opp_prop_name(struct device *dev, int process, + int volt_sel); +int rockchip_adjust_power_scale(struct device *dev, int scale); +int rockchip_get_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + unsigned long volt, u32 *target_rm); +int rockchip_set_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, u32 rm, + bool is_set_rm); +int rockchip_init_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + char *reg_name); +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); +int rockchip_init_opp_table(struct device *dev, + struct rockchip_opp_info *info, + char *lkg_name, char *reg_name); +int rockchip_opp_dump_cur_state(struct device *dev); +#else +static inline int rockchip_of_get_leakage(struct device *dev, char *lkg_name, + int *leakage) +{ + return -EOPNOTSUPP; +} + +static inline void rockchip_of_get_lkg_sel(struct device *dev, + struct device_node *np, + char *lkg_name, int process, + int *volt_sel, int *scale_sel) +{ +} + +static inline void rockchip_pvtpll_calibrate_opp(struct rockchip_opp_info *info) +{ +} + +static inline void rockchip_of_get_pvtm_sel(struct device *dev, + struct device_node *np, + char *reg_name, int process, + int *volt_sel, int *scale_sel) +{ +} + +static inline void rockchip_of_get_bin_sel(struct device *dev, + struct device_node *np, int bin, + int *scale_sel) +{ +} + +static inline void rockchip_of_get_bin_volt_sel(struct device *dev, + struct device_node *np, + int bin, int *bin_volt_sel) +{ +} + +static inline int rockchip_nvmem_cell_read_u8(struct device_node *np, + const char *cell_id, u8 *val) +{ + return -EOPNOTSUPP; +} + +static inline int rockchip_nvmem_cell_read_u16(struct device_node *np, + const char *cell_id, u16 *val) +{ + return -EOPNOTSUPP; +} + +static inline int rockchip_get_volt_rm_table(struct device *dev, + struct device_node *np, + char *porp_name, + struct volt_rm_table **table) +{ + return -EOPNOTSUPP; + +} + +static inline void rockchip_get_opp_data(const struct of_device_id *matches, + struct rockchip_opp_info *info) +{ +} + +static inline void rockchip_get_scale_volt_sel(struct device *dev, + char *lkg_name, char *reg_name, + int bin, int process, int *scale, + int *volt_sel) +{ +} + +static inline struct opp_table *rockchip_set_opp_prop_name(struct device *dev, + int process, + int volt_sel) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline int rockchip_adjust_power_scale(struct device *dev, int scale) +{ + return -EOPNOTSUPP; +} + +static inline int rockchip_get_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + unsigned long volt, u32 *target_rm) +{ + return -EOPNOTSUPP; +} +static inline int rockchip_set_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + u32 rm, bool is_set_rm) +{ + return -EOPNOTSUPP; +} + +static inline int rockchip_init_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + char *reg_name) +{ + return -EOPNOTSUPP; +} + +static inline 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) +{ + return -EOPNOTSUPP; +} + +static inline int rockchip_init_opp_table(struct device *dev, + struct rockchip_opp_info *info, + char *lkg_name, char *reg_name) +{ + return -EOPNOTSUPP; +} + +static inline int rockchip_opp_dump_cur_state(struct device *dev) +{ + return -EOPNOTSUPP; +} + +#endif /* CONFIG_ROCKCHIP_OPP */ + +#endif