diff --git a/target/linux/rockchip/files/drivers/soc/rockchip/rockchip_opp_select.c b/target/linux/rockchip/files/drivers/soc/rockchip/rockchip_opp_select.c new file mode 100644 index 0000000000..842589fb70 --- /dev/null +++ b/target/linux/rockchip/files/drivers/soc/rockchip/rockchip_opp_select.c @@ -0,0 +1,2528 @@ +/* + * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ +//#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 (!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; +} + +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 (!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 (!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; + + 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) + 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->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 (!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 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 (!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; +} + +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); + + 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); + +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 , Liang Chen "); +MODULE_LICENSE("GPL"); diff --git a/target/linux/rockchip/files/drivers/soc/rockchip/rockchip_pvtm.c b/target/linux/rockchip/files/drivers/soc/rockchip/rockchip_pvtm.c new file mode 100644 index 0000000000..75006737f4 --- /dev/null +++ b/target/linux/rockchip/files/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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/rockchip/files/include/linux/rockchip/rockchip_sip.h b/target/linux/rockchip/files/include/linux/rockchip/rockchip_sip.h new file mode 100644 index 0000000000..70b1493ef6 --- /dev/null +++ b/target/linux/rockchip/files/include/linux/rockchip/rockchip_sip.h @@ -0,0 +1,549 @@ +/* Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __ROCKCHIP_SIP_H +#define __ROCKCHIP_SIP_H + +#include +#include + +/* SMC function IDs for SiP Service queries, compatible with kernel-3.10 */ +#define SIP_ATF_VERSION 0x82000001 +#define SIP_ACCESS_REG 0x82000002 +#define SIP_SUSPEND_MODE 0x82000003 +#define SIP_PENDING_CPUS 0x82000004 +#define SIP_UARTDBG_CFG 0x82000005 +#define SIP_UARTDBG_CFG64 0xc2000005 +#define SIP_MCU_EL3FIQ_CFG 0x82000006 +#define SIP_ACCESS_CHIP_STATE64 0xc2000006 +#define SIP_SECURE_MEM_CONFIG 0x82000007 +#define SIP_ACCESS_CHIP_EXTRA_STATE64 0xc2000007 +#define SIP_DRAM_CONFIG 0x82000008 +#define SIP_SHARE_MEM 0x82000009 +#define SIP_SIP_VERSION 0x8200000a +#define SIP_REMOTECTL_CFG 0x8200000b +#define PSCI_SIP_VPU_RESET 0x8200000c +#define SIP_BUS_CFG 0x8200000d +#define SIP_LAST_LOG 0x8200000e +#define SIP_SCMI_AGENT0 0x82000010 +#define SIP_SCMI_AGENT1 0x82000011 +#define SIP_SCMI_AGENT2 0x82000012 +#define SIP_SCMI_AGENT3 0x82000013 +#define SIP_SCMI_AGENT4 0x82000014 +#define SIP_SCMI_AGENT5 0x82000015 +#define SIP_SCMI_AGENT6 0x82000016 +#define SIP_SCMI_AGENT7 0x82000017 +#define SIP_SCMI_AGENT8 0x82000018 +#define SIP_SCMI_AGENT9 0x82000019 +#define SIP_SCMI_AGENT10 0x8200001a +#define SIP_SCMI_AGENT11 0x8200001b +#define SIP_SCMI_AGENT12 0x8200001c +#define SIP_SCMI_AGENT13 0x8200001d +#define SIP_SCMI_AGENT14 0x8200001e +#define SIP_SCMI_AGENT15 0x8200001f +#define SIP_SDEI_FIQ_DBG_SWITCH_CPU 0x82000020 +#define SIP_SDEI_FIQ_DBG_GET_EVENT_ID 0x82000021 +#define RK_SIP_AMP_CFG 0x82000022 +#define RK_SIP_FIQ_CTRL 0x82000024 +#define SIP_HDCP_CONFIG 0x82000025 +#define SIP_WDT_CFG 0x82000026 +#define SIP_HDMIRX_CFG 0x82000027 +#define SIP_MCU_CFG 0x82000028 +#define SIP_PVTPLL_CFG 0x82000029 + +#define TRUSTED_OS_HDCPKEY_INIT 0xB7000003 + +/* Rockchip Sip version */ +#define SIP_IMPLEMENT_V1 (1) +#define SIP_IMPLEMENT_V2 (2) + +/* Trust firmware version */ +#define ATF_VER_MAJOR(ver) (((ver) >> 16) & 0xffff) +#define ATF_VER_MINOR(ver) (((ver) >> 0) & 0xffff) + +/* SIP_ACCESS_REG: read or write */ +#define SECURE_REG_RD 0x0 +#define SECURE_REG_WR 0x1 + +/* Fiq debugger share memory: 8KB enough */ +#define FIQ_UARTDBG_PAGE_NUMS 2 +#define FIQ_UARTDBG_SHARE_MEM_SIZE ((FIQ_UARTDBG_PAGE_NUMS) * 4096) + +/* Error return code */ +#define IS_SIP_ERROR(x) (!!(x)) + +#define SIP_RET_SUCCESS 0 +#define SIP_RET_SMC_UNKNOWN -1 +#define SIP_RET_NOT_SUPPORTED -2 +#define SIP_RET_INVALID_PARAMS -3 +#define SIP_RET_INVALID_ADDRESS -4 +#define SIP_RET_DENIED -5 +#define SIP_RET_SET_RATE_TIMEOUT -6 + +/* SIP_UARTDBG_CFG64 call types */ +#define UARTDBG_CFG_INIT 0xf0 +#define UARTDBG_CFG_OSHDL_TO_OS 0xf1 +#define UARTDBG_CFG_OSHDL_CPUSW 0xf3 +#define UARTDBG_CFG_OSHDL_DEBUG_ENABLE 0xf4 +#define UARTDBG_CFG_OSHDL_DEBUG_DISABLE 0xf5 +#define UARTDBG_CFG_PRINT_PORT 0xf7 +#define UARTDBG_CFG_FIQ_ENABEL 0xf8 +#define UARTDBG_CFG_FIQ_DISABEL 0xf9 + +/* SIP_SUSPEND_MODE32 call types */ +#define SUSPEND_MODE_CONFIG 0x01 +#define WKUP_SOURCE_CONFIG 0x02 +#define PWM_REGULATOR_CONFIG 0x03 +#define GPIO_POWER_CONFIG 0x04 +#define SUSPEND_DEBUG_ENABLE 0x05 +#define APIOS_SUSPEND_CONFIG 0x06 +#define VIRTUAL_POWEROFF 0x07 +#define SUSPEND_WFI_TIME_MS 0x08 +#define LINUX_PM_STATE 0x09 +#define SUSPEND_IO_RET_CONFIG 0x0a +#define SLEEP_PIN_CONFIG 0x0b + +/* SIP_REMOTECTL_CFG call types */ +#define REMOTECTL_SET_IRQ 0xf0 +#define REMOTECTL_SET_PWM_CH 0xf1 +#define REMOTECTL_SET_PWRKEY 0xf2 +#define REMOTECTL_GET_WAKEUP_STATE 0xf3 +#define REMOTECTL_ENABLE 0xf4 +/* wakeup state */ +#define REMOTECTL_PWRKEY_WAKEUP 0xdeadbeaf + +/* SIP_MCU_CFG child configs, MCU ID */ +enum { + RK_BUS_MCU, + RK_PMU_MCU, + RK_DDR_MCU, + RK_NPU_MCU, +}; + +#define RK_SIP_MCU_ID(type, id) ((type) << 8 | id) + +#define RK_SIP_CFG_BUSMCU_0_ID RK_SIP_MCU_ID(RK_BUS_MCU, 0) +#define RK_SIP_CFG_BUSMCU_1_ID RK_SIP_MCU_ID(RK_BUS_MCU, 1) +#define RK_SIP_CFG_PMUMCU_0_ID RK_SIP_MCU_ID(RK_PMU_MCU, 0) +#define RK_SIP_CFG_DDRMCU_0_ID RK_SIP_MCU_ID(RK_DDR_MCU, 0) +#define RK_SIP_CFG_NPUMCU_0_ID RK_SIP_MCU_ID(RK_NPU_MCU, 0) + +/* SIP_MCU_CFG child configs */ +#define CONFIG_MCU_CODE_START_ADDR 0x01 +#define CONFIG_MCU_EXPERI_START_ADDR 0x02 +#define CONFIG_MCU_SRAM_START_ADDR 0x03 +#define CONFIG_MCU_EXSRAM_START_ADDR 0x04 + +struct dram_addrmap_info { + u64 ch_mask[2]; + u64 bk_mask[4]; + u64 bg_mask[2]; + u64 cs_mask[2]; + u32 reserved[20]; + u32 bank_bit_first; + u32 bank_bit_mask; +}; + +/* AMP Ctrl */ +enum { + RK_AMP_SUB_FUNC_CFG_MODE = 0, + RK_AMP_SUB_FUNC_BOOT_ARG01, + RK_AMP_SUB_FUNC_BOOT_ARG23, + RK_AMP_SUB_FUNC_REQ_CPU_OFF, + RK_AMP_SUB_FUNC_GET_CPU_STATUS, + RK_AMP_SUB_FUNC_RSV, /* for RTOS */ + RK_AMP_SUB_FUNC_CPU_ON, + RK_AMP_SUB_FUNC_END, +}; + +enum { + FIRMWARE_NONE, + FIRMWARE_TEE_32BIT, + FIRMWARE_ATF_32BIT, + FIRMWARE_ATF_64BIT, + FIRMWARE_END, +}; + +/* Share mem page types */ +typedef enum { + SHARE_PAGE_TYPE_INVALID = 0, + SHARE_PAGE_TYPE_UARTDBG, + SHARE_PAGE_TYPE_DDR, + SHARE_PAGE_TYPE_DDRDBG, + SHARE_PAGE_TYPE_DDRECC, + SHARE_PAGE_TYPE_DDRFSP, + SHARE_PAGE_TYPE_DDR_ADDRMAP, + SHARE_PAGE_TYPE_LAST_LOG, + SHARE_PAGE_TYPE_HDCP, + SHARE_PAGE_TYPE_SLEEP, + SHARE_PAGE_TYPE_MAX, +} share_page_type_t; + +/* fiq control sub func */ +enum { + RK_SIP_FIQ_CTRL_FIQ_EN = 1, + RK_SIP_FIQ_CTRL_FIQ_DIS, + RK_SIP_FIQ_CTRL_SET_AFF +}; + +/* hdcp function types */ +enum { + HDCP_FUNC_STORAGE_INCRYPT = 1, + HDCP_FUNC_KEY_LOAD, + HDCP_FUNC_ENCRYPT_MODE +}; + +/* support hdcp device list */ +enum { + DP_TX0, + DP_TX1, + EDP_TX0, + EDP_TX1, + HDMI_TX0, + HDMI_TX1, + HDMI_RX, + MAX_DEVICE, +}; + +/* SIP_WDT_CONFIG call types */ +enum { + WDT_START = 0, + WDT_STOP = 1, + WDT_PING = 2, +}; + +/* SIP_HDMIRX_CONFIG child configs */ +enum { + HDMIRX_AUTO_TOUCH_EN = 0, + HDMIRX_REG_PRE_FETCH = 1, + HDMIRX_INFO_NOTIFY = 2, +}; + +/* SIP_PVTPLL_CFG child configs */ +enum { + PVTPLL_GET_INFO = 0, + PVTPLL_ADJUST_TABLE = 1, + PVTPLL_LOW_TEMP = 2, +}; + +struct pt_regs; +typedef void (*sip_fiq_debugger_uart_irq_tf_cb_t)(struct pt_regs *_pt_regs, unsigned long cpu); + +/* + * Rules: struct arm_smccc_res contains result and data, details: + * + * a0: error code(0: success, !0: error); + * a1~a3: data + */ +#if IS_REACHABLE(CONFIG_ROCKCHIP_SIP) +struct arm_smccc_res sip_smc_get_atf_version(void); +struct arm_smccc_res sip_smc_get_sip_version(void); +struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2); +struct arm_smccc_res sip_smc_request_share_mem(u32 page_num, + share_page_type_t page_type); +struct arm_smccc_res sip_smc_mcu_el3fiq(u32 arg0, u32 arg1, u32 arg2); +struct arm_smccc_res sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2); +struct arm_smccc_res sip_smc_get_suspend_info(u32 info); +struct arm_smccc_res sip_smc_lastlog_request(void); + +int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2); +int sip_smc_virtual_poweroff(void); +int sip_smc_remotectl_config(u32 func, u32 data); + +int sip_smc_secure_reg_write(u32 addr_phy, u32 val); +u32 sip_smc_secure_reg_read(u32 addr_phy); +struct arm_smccc_res sip_smc_bus_config(u32 arg0, u32 arg1, u32 arg2); +struct dram_addrmap_info *sip_smc_get_dram_map(void); +int sip_smc_amp_config(u32 sub_func_id, u32 arg1, u32 arg2, u32 arg3); +struct arm_smccc_res sip_smc_get_amp_info(u32 sub_func_id, u32 arg1); +struct arm_smccc_res sip_smc_get_pvtpll_info(u32 sub_func_id, u32 arg1); +struct arm_smccc_res sip_smc_pvtpll_config(u32 sub_func_id, u32 arg1, u32 arg2, + u32 arg3, u32 arg4, u32 arg5, u32 arg6); + +void __iomem *sip_hdcp_request_share_memory(int id); +struct arm_smccc_res sip_hdcp_config(u32 arg0, u32 arg1, u32 arg2); +ulong sip_cpu_logical_map_mpidr(u32 cpu); +/***************************fiq debugger **************************************/ +void sip_fiq_debugger_enable_fiq(bool enable, uint32_t tgt_cpu); +void sip_fiq_debugger_enable_debug(bool enable); +int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id, sip_fiq_debugger_uart_irq_tf_cb_t callback_fn); +int sip_fiq_debugger_set_print_port(u32 port_phyaddr, u32 baudrate); +int sip_fiq_debugger_request_share_memory(void); +int sip_fiq_debugger_get_target_cpu(void); +int sip_fiq_debugger_switch_cpu(u32 cpu); +int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu, u32 flag); +int sip_fiq_debugger_is_enabled(void); +int sip_fiq_debugger_sdei_get_event_id(u32 *fiq, u32 *sw_cpu, u32 *flag); +int sip_fiq_control(u32 sub_func, u32 irq, unsigned long data); +int sip_wdt_config(u32 sub_func, u32 arg1, u32 arg2, u32 arg3); +int sip_hdmirx_config(u32 sub_func, u32 arg1, u32 arg2, u32 arg3); +int sip_hdcpkey_init(u32 hdcp_id); +int sip_smc_mcu_config(unsigned long mcu_id, unsigned long func, unsigned long arg2); +#else +static inline struct arm_smccc_res sip_smc_get_atf_version(void) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline struct arm_smccc_res sip_smc_get_sip_version(void) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline struct arm_smccc_res sip_smc_request_share_mem + (u32 page_num, share_page_type_t page_type) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline struct arm_smccc_res sip_smc_mcu_el3fiq + (u32 arg0, u32 arg1, u32 arg2) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline struct arm_smccc_res +sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline struct arm_smccc_res sip_smc_get_suspend_info(u32 info) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline struct arm_smccc_res sip_smc_lastlog_request(void) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2) +{ + return 0; +} + +static inline int sip_smc_virtual_poweroff(void) { return 0; } +static inline int sip_smc_remotectl_config(u32 func, u32 data) { return 0; } +static inline int sip_smc_secure_reg_write(u32 addr_phy, u32 val) { return 0; } +static inline u32 sip_smc_secure_reg_read(u32 addr_phy) { return 0; } + +static inline struct arm_smccc_res sip_smc_bus_config(u32 arg0, u32 arg1, u32 arg2) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + return tmp; +} + +static inline struct dram_addrmap_info *sip_smc_get_dram_map(void) +{ + return NULL; +} + +static inline int sip_smc_amp_config(u32 sub_func_id, + u32 arg1, + u32 arg2, + u32 arg3) +{ + return 0; +} + +static inline struct arm_smccc_res sip_smc_get_amp_info(u32 sub_func_id, + u32 arg1) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED, }; + + return tmp; +} + +static inline struct arm_smccc_res sip_smc_get_pvtpll_info(u32 sub_func_id, + u32 arg1) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED, }; + + return tmp; +} + +static inline struct arm_smccc_res sip_smc_pvtpll_config(u32 sub_func_id, + u32 arg1, u32 arg2, + u32 arg3, u32 arg4, + u32 arg5, u32 arg6) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED, }; + + return tmp; +} + +static inline void __iomem *sip_hdcp_request_share_memory(int id) +{ + return NULL; +} + +static inline struct arm_smccc_res sip_hdcp_config(u32 arg0, u32 arg1, u32 arg2) +{ + struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED }; + + return tmp; +} + +static inline ulong sip_cpu_logical_map_mpidr(u32 cpu) { return 0; } + +/***************************fiq debugger **************************************/ +static inline void sip_fiq_debugger_enable_fiq + (bool enable, uint32_t tgt_cpu) { return; } + +static inline void sip_fiq_debugger_enable_debug(bool enable) { return; } +static inline int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id, + sip_fiq_debugger_uart_irq_tf_cb_t callback_fn) +{ + return 0; +} + +static inline int sip_fiq_debugger_set_print_port(u32 port_phyaddr, + u32 baudrate) +{ + return 0; +} + +static inline int sip_fiq_debugger_request_share_memory(void) { return 0; } +static inline int sip_fiq_debugger_get_target_cpu(void) { return 0; } +static inline int sip_fiq_debugger_switch_cpu(u32 cpu) { return 0; } +static inline int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu, + u32 flag) { return 0; } +static inline int sip_fiq_debugger_is_enabled(void) { return 0; } +static inline int sip_fiq_debugger_sdei_get_event_id(u32 *fiq, u32 *sw_cpu, u32 *flag) +{ + return SIP_RET_NOT_SUPPORTED; +} + +static inline int sip_fiq_control(u32 sub_func, u32 irq, unsigned long data) +{ + return 0; +} + +static inline int sip_wdt_config(u32 sub_func, + u32 arg1, + u32 arg2, + u32 arg3) +{ + return 0; +} + +static inline int sip_hdmirx_config(u32 sub_func, + u32 arg1, + u32 arg2, + u32 arg3) +{ + return SIP_RET_NOT_SUPPORTED; +} + +static inline int sip_hdcpkey_init(u32 hdcp_id) +{ + return 0; +} + +static inline int sip_smc_mcu_config(unsigned long mcu_id, + unsigned long func, + unsigned long arg2) +{ + return SIP_RET_NOT_SUPPORTED; +} +#endif + +/* 32-bit OP-TEE context, never change order of members! */ +struct sm_nsec_ctx { + u32 usr_sp; + u32 usr_lr; + u32 irq_spsr; + u32 irq_sp; + u32 irq_lr; + u32 fiq_spsr; + u32 fiq_sp; + u32 fiq_lr; + u32 svc_spsr; + u32 svc_sp; + u32 svc_lr; + u32 abt_spsr; + u32 abt_sp; + u32 abt_lr; + u32 und_spsr; + u32 und_sp; + u32 und_lr; + u32 mon_lr; + u32 mon_spsr; + u32 r4; + u32 r5; + u32 r6; + u32 r7; + u32 r8; + u32 r9; + u32 r10; + u32 r11; + u32 r12; + u32 r0; + u32 r1; + u32 r2; + u32 r3; +}; + +/* 64-bit ATF context, never change order of members! */ +struct gp_regs_ctx { + u64 x0; + u64 x1; + u64 x2; + u64 x3; + u64 x4; + u64 x5; + u64 x6; + u64 x7; + u64 x8; + u64 x9; + u64 x10; + u64 x11; + u64 x12; + u64 x13; + u64 x14; + u64 x15; + u64 x16; + u64 x17; + u64 x18; + u64 x19; + u64 x20; + u64 x21; + u64 x22; + u64 x23; + u64 x24; + u64 x25; + u64 x26; + u64 x27; + u64 x28; + u64 x29; + u64 lr; + u64 sp_el0; + u64 scr_el3; + u64 runtime_sp; + u64 spsr_el3; + u64 elr_el3; +}; + +#endif diff --git a/target/linux/rockchip/files/include/linux/soc/rockchip/pvtm.h b/target/linux/rockchip/files/include/linux/soc/rockchip/pvtm.h new file mode 100644 index 0000000000..3d2495cfd6 --- /dev/null +++ b/target/linux/rockchip/files/include/linux/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/target/linux/rockchip/files/include/soc/rockchip/rockchip_opp_select.h b/target/linux/rockchip/files/include/soc/rockchip/rockchip_opp_select.h new file mode 100644 index 0000000000..c8355ff46d --- /dev/null +++ b/target/linux/rockchip/files/include/soc/rockchip/rockchip_opp_select.h @@ -0,0 +1,296 @@ +/* + * 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 + +#include + +#define VOLT_RM_TABLE_END ~1 + +/* + * [0]: set intermediate rate + * [1]: scaling up rate or scaling down rate + * [1]: add length for pvtpll + * [2:5]: length + * [2]: use low length for pvtpll + * [3:5]: reserved + */ +#define OPP_RATE_MASK 0x3f + +/* Set intermediate rate */ +#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 + +/* Add length for pvtpll */ +#define OPP_ADD_LENGTH BIT(1) +#define OPP_LENGTH_MASK 0xf +#define OPP_LENGTH_SHIFT 2 + +/* Use low length for pvtpll */ +#define OPP_LENGTH_LOW BIT(2) + +struct rockchip_opp_info; + +struct volt_rm_table { + int volt; + int rm; +}; + +struct rockchip_opp_data { + config_clks_t config_clks; + config_regulators_t config_regulators; + + 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, + struct rockchip_opp_info *info); + int (*set_read_margin)(struct device *dev, + struct rockchip_opp_info *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 - Rockchip opp info structure + * @dev: The device. + * @dvfs_mutex: Mutex to protect changing volage and scmi clock rate. + * @data: Device-specific opp data. + * @opp_table: Temporary opp table only used when enable pvtpll calibration. + * @pvtpll_avg_offset: Register offset of pvtm value. + * @pvtpll_min_rate: Minimum frequency which needs calibration. + * @pvtpll_volt_step: Voltage step of pvtpll calibration. + * @volt_rm_tbl: Pointer to voltage to memory read margin conversion table. + * @grf: General Register Files regmap. + * @dsu_grf: DSU General Register Files regmap. + * @cci_grf: CCI General Register Files regmap. + * @clocks: Pvtpll clocks. + * @nclocks: Number of pvtpll clock. + * @intermediate_threshold_freq: The frequency threshold of intermediate rate. + * @low_rm: The read margin threshold of intermediate rate. + * @current_rm: Current memory read margin. + * @target_rm: Target memory read margin. + * @is_runtime_active: Marks if device's pd is power on. + * @opp_token: Integer replacement for opp_table. + * @scale: Frequency scale. + * @bin: Soc version. + * @process: Process version. + * @volt_sel: Speed grade. + * @supported_hw: Array of version number to support. + * @clk: Device's clock handle. + * @is_scmi_clk: Marks if device's clock is scmi clock. + * @regulators: Supply regulators. + * @regulator_count: Number of power supply regulators. + * @init_freq: Set the initial frequency when init opp table. + * @is_rate_volt_checked: Marks if device has checked initial rate and voltage. + * @pvtpll_clk_id: Device's clock id. + * @pvtpll_low_temp: Marks if device has low temperature pvtpll config. + */ +struct rockchip_opp_info { + struct device *dev; + struct mutex dvfs_mutex; + const struct rockchip_opp_data *data; + struct pvtpll_opp_table *opp_table; + struct regmap *pvtpll_base; + unsigned int pvtpll_avg_offset; + unsigned int pvtpll_min_rate; + unsigned int pvtpll_volt_step; + + struct volt_rm_table *volt_rm_tbl; + struct regmap *grf; + union { + struct regmap *dsu_grf; + struct regmap *cci_grf; + }; + struct clk_bulk_data *clocks; + int nclocks; + unsigned long intermediate_threshold_freq; + u32 low_rm; + u32 current_rm; + u32 target_rm; + bool is_runtime_active; + + int opp_token; + int scale; + int bin; + int process; + int volt_sel; + u32 supported_hw[2]; + + struct clk *clk; + bool is_scmi_clk; + struct regulator **regulators; + int regulator_count; + unsigned int init_freq; + bool is_rate_volt_checked; + + u32 pvtpll_clk_id; + bool pvtpll_low_temp; +}; + +#if IS_ENABLED(CONFIG_ROCKCHIP_OPP) +int rockchip_of_get_leakage(struct device *dev, char *lkg_name, int *leakage); +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); +void rockchip_get_opp_data(const struct of_device_id *matches, + struct rockchip_opp_info *info); +void rockchip_opp_dvfs_lock(struct rockchip_opp_info *info); +void rockchip_opp_dvfs_unlock(struct rockchip_opp_info *info); +int rockchip_init_opp_info(struct device *dev, struct rockchip_opp_info *info, + char *clk_name, char *reg_name); +void rockchip_uninit_opp_info(struct device *dev, struct rockchip_opp_info *info); +int rockchip_adjust_opp_table(struct device *dev, struct rockchip_opp_info *info); +int rockchip_get_read_margin(struct device *dev, + struct rockchip_opp_info *info, + unsigned long volt, u32 *target_rm); +int rockchip_set_read_margin(struct device *dev, + struct rockchip_opp_info *info, u32 rm, + bool is_set_rm); +int rockchip_set_intermediate_rate(struct device *dev, + struct rockchip_opp_info *info, + struct clk *clk, unsigned long old_freq, + unsigned long new_freq, bool is_scaling_up, + bool is_set_clk); +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); +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); +int rockchip_opp_check_rate_volt(struct device *dev, struct rockchip_opp_info *info); +int rockchip_init_opp_table(struct device *dev, struct rockchip_opp_info *info, + char *clk_name, char *reg_name); +void rockchip_uninit_opp_table(struct device *dev, + struct rockchip_opp_info *info); +#else +static inline int rockchip_of_get_leakage(struct device *dev, char *lkg_name, + int *leakage) +{ + return -EOPNOTSUPP; +} + +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 void rockchip_get_opp_data(const struct of_device_id *matches, + struct rockchip_opp_info *info) +{ +} + +static inline void rockchip_opp_dvfs_lock(struct rockchip_opp_info *info) +{ +} + +static inline void rockchip_opp_dvfs_unlock(struct rockchip_opp_info *info) +{ +} + +static inline int +rockchip_init_opp_info(struct device *dev, struct rockchip_opp_info *info, + char *clk_name, char *reg_name) +{ + return -EOPNOTSUPP; +} + +static inline void +rockchip_uninit_opp_info(struct device *dev, struct rockchip_opp_info *info) +{ +} + +static inline int +rockchip_adjust_opp_table(struct device *dev, struct rockchip_opp_info *info) +{ + return -EOPNOTSUPP; +} + +static inline int rockchip_get_read_margin(struct device *dev, + struct rockchip_opp_info *info, + unsigned long volt, u32 *target_rm) +{ + return -EOPNOTSUPP; +} +static inline int rockchip_set_read_margin(struct device *dev, + struct rockchip_opp_info *info, + u32 rm, bool is_set_rm) +{ + 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_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) +{ + return -EOPNOTSUPP; +} + +static inline 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) +{ + return -EOPNOTSUPP; +} + +static inline int rockchip_opp_check_rate_volt(struct device *dev, + struct rockchip_opp_info *info) +{ + return -EOPNOTSUPP; +} + +static inline int +rockchip_init_opp_table(struct device *dev, struct rockchip_opp_info *info, + char *clk_name, char *reg_name) +{ + return -EOPNOTSUPP; +} + +static inline void rockchip_uninit_opp_table(struct device *dev, + struct rockchip_opp_info *info) +{ +} + +#endif /* CONFIG_ROCKCHIP_OPP */ + +#endif diff --git a/target/linux/rockchip/patches-6.6/810-rockchip-clk-api.patch b/target/linux/rockchip/patches-6.6/810-rockchip-clk-api.patch new file mode 100644 index 0000000000..39af977f50 --- /dev/null +++ b/target/linux/rockchip/patches-6.6/810-rockchip-clk-api.patch @@ -0,0 +1,234 @@ +From 9ebada0adea894db97a9460180860e45ca4d2f78 Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Fri, 21 Mar 2025 16:31:21 +0800 +Subject: [PATCH] rockchip/clk: add rockchip_pll_clk api + +--- + drivers/clk/rockchip/clk-pll.c | 110 +++++++++++++++++++++++++++++++-- + drivers/clk/rockchip/clk.h | 3 + + 2 files changed, 109 insertions(+), 4 deletions(-) + +diff --git a/drivers/clk/rockchip/clk-pll.c b/drivers/clk/rockchip/clk-pll.c +index 2d42eb628..5b01660cb 100644 +--- a/drivers/clk/rockchip/clk-pll.c ++++ b/drivers/clk/rockchip/clk-pll.c +@@ -38,6 +38,8 @@ struct rockchip_clk_pll { + u8 flags; + const struct rockchip_pll_rate_table *rate_table; + unsigned int rate_count; ++ int sel; ++ unsigned long scaling; + spinlock_t *lock; + + struct rockchip_clk_provider *ctx; +@@ -47,6 +49,72 @@ struct rockchip_clk_pll { + #define to_rockchip_clk_pll_nb(nb) \ + container_of(nb, struct rockchip_clk_pll, clk_nb) + ++int rockchip_pll_clk_adaptive_scaling(struct clk *clk, int sel) ++{ ++ struct clk *parent = clk_get_parent(clk); ++ struct rockchip_clk_pll *pll; ++ ++ if (IS_ERR_OR_NULL(parent)) ++ return -EINVAL; ++ ++ pll = to_rockchip_clk_pll(__clk_get_hw(parent)); ++ if (!pll) ++ return -EINVAL; ++ ++ pll->sel = sel; ++ ++ return 0; ++} ++EXPORT_SYMBOL(rockchip_pll_clk_adaptive_scaling); ++ ++int rockchip_pll_clk_rate_to_scale(struct clk *clk, unsigned long rate) ++{ ++ const struct rockchip_pll_rate_table *rate_table; ++ struct clk *parent = clk_get_parent(clk); ++ struct rockchip_clk_pll *pll; ++ unsigned int i; ++ ++ if (IS_ERR_OR_NULL(parent)) ++ return -EINVAL; ++ ++ pll = to_rockchip_clk_pll(__clk_get_hw(parent)); ++ if (!pll) ++ return -EINVAL; ++ ++ rate_table = pll->rate_table; ++ for (i = 0; i < pll->rate_count; i++) { ++ if (rate >= rate_table[i].rate) ++ return i; ++ } ++ ++ return -EINVAL; ++} ++EXPORT_SYMBOL(rockchip_pll_clk_rate_to_scale); ++ ++int rockchip_pll_clk_scale_to_rate(struct clk *clk, unsigned int scale) ++{ ++ const struct rockchip_pll_rate_table *rate_table; ++ struct clk *parent = clk_get_parent(clk); ++ struct rockchip_clk_pll *pll; ++ unsigned int i; ++ ++ if (IS_ERR_OR_NULL(parent)) ++ return -EINVAL; ++ ++ pll = to_rockchip_clk_pll(__clk_get_hw(parent)); ++ if (!pll) ++ return -EINVAL; ++ ++ rate_table = pll->rate_table; ++ for (i = 0; i < pll->rate_count; i++) { ++ if (i == scale) ++ return rate_table[i].rate; ++ } ++ ++ return -EINVAL; ++} ++EXPORT_SYMBOL(rockchip_pll_clk_scale_to_rate); ++ + static const struct rockchip_pll_rate_table *rockchip_get_pll_settings( + struct rockchip_clk_pll *pll, unsigned long rate) + { +@@ -54,9 +122,16 @@ static const struct rockchip_pll_rate_table *rockchip_get_pll_settings( + int i; + + for (i = 0; i < pll->rate_count; i++) { +- if (rate == rate_table[i].rate) ++ if (rate == rate_table[i].rate) { ++ if (i < pll->sel) { ++ pll->scaling = rate; ++ return &rate_table[pll->sel]; ++ } ++ pll->scaling = 0; + return &rate_table[i]; ++ } + } ++ pll->scaling = 0; + + return NULL; + } +@@ -167,6 +242,9 @@ static unsigned long rockchip_rk3036_pll_recalc_rate(struct clk_hw *hw, + struct rockchip_pll_rate_table cur; + u64 rate64 = prate; + ++ if (pll->sel && pll->scaling) ++ return pll->scaling; ++ + rockchip_rk3036_pll_get_params(pll, &cur); + + rate64 *= cur.fbdiv; +@@ -412,6 +490,9 @@ static unsigned long rockchip_rk3066_pll_recalc_rate(struct clk_hw *hw, + return prate; + } + ++ if (pll->sel && pll->scaling) ++ return pll->scaling; ++ + rockchip_rk3066_pll_get_params(pll, &cur); + + rate64 *= cur.nf; +@@ -485,6 +566,7 @@ static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate, + { + struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw); + const struct rockchip_pll_rate_table *rate; ++ int ret; + + pr_debug("%s: changing %s to %lu with a parent rate of %lu\n", + __func__, clk_hw_get_name(hw), drate, prate); +@@ -497,7 +579,11 @@ static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate, + return -EINVAL; + } + +- return rockchip_rk3066_pll_set_params(pll, rate); ++ ret = rockchip_rk3066_pll_set_params(pll, rate); ++ if (ret) ++ pll->scaling = 0; ++ ++ return ret; + } + + static int rockchip_rk3066_pll_enable(struct clk_hw *hw) +@@ -649,6 +735,9 @@ static unsigned long rockchip_rk3399_pll_recalc_rate(struct clk_hw *hw, + struct rockchip_pll_rate_table cur; + u64 rate64 = prate; + ++ if (pll->sel && pll->scaling) ++ return pll->scaling; ++ + rockchip_rk3399_pll_get_params(pll, &cur); + + rate64 *= cur.fbdiv; +@@ -734,6 +823,7 @@ static int rockchip_rk3399_pll_set_rate(struct clk_hw *hw, unsigned long drate, + { + struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw); + const struct rockchip_pll_rate_table *rate; ++ int ret; + + pr_debug("%s: changing %s to %lu with a parent rate of %lu\n", + __func__, __clk_get_name(hw->clk), drate, prate); +@@ -746,7 +836,11 @@ static int rockchip_rk3399_pll_set_rate(struct clk_hw *hw, unsigned long drate, + return -EINVAL; + } + +- return rockchip_rk3399_pll_set_params(pll, rate); ++ ret = rockchip_rk3399_pll_set_params(pll, rate); ++ if (ret) ++ pll->scaling = 0; ++ ++ return ret; + } + + static int rockchip_rk3399_pll_enable(struct clk_hw *hw) +@@ -899,6 +993,9 @@ static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned + struct rockchip_pll_rate_table cur; + u64 rate64 = prate, postdiv; + ++ if (pll->sel && pll->scaling) ++ return pll->scaling; ++ + rockchip_rk3588_pll_get_params(pll, &cur); + + rate64 *= cur.m; +@@ -980,6 +1077,7 @@ static int rockchip_rk3588_pll_set_rate(struct clk_hw *hw, unsigned long drate, + { + struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw); + const struct rockchip_pll_rate_table *rate; ++ int ret; + + pr_debug("%s: changing %s to %lu with a parent rate of %lu\n", + __func__, __clk_get_name(hw->clk), drate, prate); +@@ -992,7 +1090,11 @@ static int rockchip_rk3588_pll_set_rate(struct clk_hw *hw, unsigned long drate, + return -EINVAL; + } + +- return rockchip_rk3588_pll_set_params(pll, rate); ++ ret = rockchip_rk3588_pll_set_params(pll, rate); ++ if (ret) ++ pll->scaling = 0; ++ ++ return ret; + } + + static int rockchip_rk3588_pll_enable(struct clk_hw *hw) +diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h +index fd3b476de..1bcfc39fb 100644 +--- a/drivers/clk/rockchip/clk.h ++++ b/drivers/clk/rockchip/clk.h +@@ -987,6 +987,9 @@ void rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx, + const struct rockchip_cpuclk_reg_data *reg_data, + const struct rockchip_cpuclk_rate_table *rates, + int nrates); ++int rockchip_pll_clk_rate_to_scale(struct clk *clk, unsigned long rate); ++int rockchip_pll_clk_scale_to_rate(struct clk *clk, unsigned int scale); ++int rockchip_pll_clk_adaptive_scaling(struct clk *clk, int sel); + void rockchip_clk_protect_critical(const char *const clocks[], int nclocks); + void rockchip_register_restart_notifier(struct rockchip_clk_provider *ctx, + unsigned int reg, void (*cb)(void)); +-- +2.46.0 + diff --git a/target/linux/rockchip/patches-6.6/811-drivers-add-rockchip-opp.patch b/target/linux/rockchip/patches-6.6/811-drivers-add-rockchip-opp.patch new file mode 100644 index 0000000000..00bed67814 --- /dev/null +++ b/target/linux/rockchip/patches-6.6/811-drivers-add-rockchip-opp.patch @@ -0,0 +1,37 @@ +From 2e7f97d4228b45b6be6895490b9bbeb67a952c14 Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Fri, 21 Mar 2025 18:09:43 +0800 +Subject: [PATCH] drivers: add rockchip opp + +--- + drivers/soc/rockchip/Kconfig | 6 ++++++ + drivers/soc/rockchip/Makefile | 1 + + 2 files changed, 7 insertions(+) + +diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig +index aff2f7e95..4ec085435 100644 +--- a/drivers/soc/rockchip/Kconfig ++++ b/drivers/soc/rockchip/Kconfig +@@ -42,4 +42,10 @@ 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. ++ + endif +diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile +index 23d414433..7fa6f324d 100644 +--- a/drivers/soc/rockchip/Makefile ++++ b/drivers/soc/rockchip/Makefile +@@ -5,3 +5,4 @@ + 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 +-- +2.46.0 + diff --git a/target/linux/rockchip/patches-6.6/812-drivers-add-rockchip-pvtm.patch b/target/linux/rockchip/patches-6.6/812-drivers-add-rockchip-pvtm.patch new file mode 100644 index 0000000000..90bd13f920 --- /dev/null +++ b/target/linux/rockchip/patches-6.6/812-drivers-add-rockchip-pvtm.patch @@ -0,0 +1,39 @@ +From ea097df4006ad966e37db6205dc97346e1432748 Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Fri, 21 Mar 2025 18:10:15 +0800 +Subject: [PATCH] drivers: add rockchip pvtm + +--- + drivers/soc/rockchip/Kconfig | 8 ++++++++ + drivers/soc/rockchip/Makefile | 1 + + 2 files changed, 9 insertions(+) + +diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig +index 4ec085435..152f241f2 100644 +--- a/drivers/soc/rockchip/Kconfig ++++ b/drivers/soc/rockchip/Kconfig +@@ -48,4 +48,12 @@ config ROCKCHIP_OPP + 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 7fa6f324d..c1340db95 100644 +--- a/drivers/soc/rockchip/Makefile ++++ b/drivers/soc/rockchip/Makefile +@@ -6,3 +6,4 @@ 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 +-- +2.46.0 +