/* * 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 (IS_ERR(opp_table)) { kfree(info->opp_table); info->opp_table = NULL; return PTR_ERR(opp_table); } mutex_lock(&opp_table->lock); list_for_each_entry(opp, &opp_table->opp_list, node) { if (!opp->available) continue; info->opp_table[i].u_volt = opp->supplies[0].u_volt; info->opp_table[i].u_volt_min = opp->supplies[0].u_volt_min; info->opp_table[i].u_volt_max = opp->supplies[0].u_volt_max; if (opp_table->regulator_count > 1) { info->opp_table[i].u_volt_mem = opp->supplies[1].u_volt; info->opp_table[i].u_volt_mem_min = opp->supplies[1].u_volt_min; info->opp_table[i].u_volt_mem_max = opp->supplies[1].u_volt_max; } info->opp_table[i++].rate = opp->rates[0]; } mutex_unlock(&opp_table->lock); dev_pm_opp_put_opp_table(opp_table); return 0; } static int rockchip_pvtpll_set_volt(struct device *dev, struct regulator *reg, int target_uV, int max_uV, char *reg_name) { int ret = 0; ret = regulator_set_voltage(reg, target_uV, max_uV); if (ret) dev_err(dev, "%s: failed to set %s voltage (%d %d uV): %d\n", __func__, reg_name, target_uV, max_uV, ret); return ret; } static int rockchip_pvtpll_set_clk(struct device *dev, struct clk *clk, unsigned long rate) { int ret = 0; ret = clk_set_rate(clk, rate); if (ret) dev_err(dev, "%s: failed to set rate %lu Hz, ret:%d\n", __func__, rate, ret); return ret; } static void rockchip_pvtpll_calibrate_opp(struct rockchip_opp_info *info) { struct opp_table *opp_table; struct dev_pm_opp *opp; struct regulator *reg = NULL, *reg_mem = NULL; unsigned long old_volt = 0, old_volt_mem = 0; unsigned long volt = 0, volt_mem = 0; unsigned long volt_min, volt_max, volt_mem_min, volt_mem_max; unsigned long rate, pvtpll_rate, old_rate, cur_rate, delta0, delta1; int i = 0, max_count, step, cur_step, ret; if (!info || !info->pvtpll_base) return; dev_dbg(info->dev, "calibrating opp ...\n"); ret = rockchip_init_pvtpll_info(info); if (ret) return; max_count = dev_pm_opp_get_opp_count(info->dev); if (max_count <= 0) return; opp_table = dev_pm_opp_get_opp_table(info->dev); if (IS_ERR(opp_table)) return; if (info->clocks) { ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); if (ret) { dev_err(info->dev, "failed to enable opp clks\n"); return; } } if ((!opp_table->regulators) || IS_ERR(opp_table->clk)) goto out_put; reg = opp_table->regulators[0]; old_volt = regulator_get_voltage(reg); if (opp_table->regulator_count > 1) { reg_mem = opp_table->regulators[1]; old_volt_mem = regulator_get_voltage(reg_mem); if (IS_ERR_VALUE(old_volt_mem)) goto out_put; } old_rate = clk_get_rate(opp_table->clk); if (IS_ERR_VALUE(old_volt) || IS_ERR_VALUE(old_rate)) goto out_put; cur_rate = old_rate; step = regulator_get_linear_step(reg); if (!step || info->pvtpll_volt_step > step) step = info->pvtpll_volt_step; if (old_rate > info->pvtpll_min_rate * 1000) { if (rockchip_pvtpll_set_clk(info->dev, opp_table->clk, info->pvtpll_min_rate * 1000)) goto out_put; } for (i = 0; i < max_count; i++) { rate = info->opp_table[i].rate; if (rate < 1000 * info->pvtpll_min_rate) continue; volt = max(volt, info->opp_table[i].u_volt); volt_min = info->opp_table[i].u_volt_min; volt_max = info->opp_table[i].u_volt_max; if (opp_table->regulator_count > 1) { volt_mem = max(volt_mem, info->opp_table[i].u_volt_mem); volt_mem_min = info->opp_table[i].u_volt_mem_min; volt_mem_max = info->opp_table[i].u_volt_mem_max; if (rockchip_pvtpll_set_volt(info->dev, reg_mem, volt_mem, volt_mem_max, "mem")) goto out; } if (rockchip_pvtpll_set_volt(info->dev, reg, volt, volt_max, "vdd")) goto out; if (rockchip_pvtpll_set_clk(info->dev, opp_table->clk, rate)) goto out; cur_rate = rate; pvtpll_rate = rockchip_pvtpll_get_rate(info); if (!pvtpll_rate) goto out; cur_step = (pvtpll_rate < rate) ? step : -step; delta1 = abs(pvtpll_rate - rate); do { delta0 = delta1; volt += cur_step; if ((volt < volt_min) || (volt > volt_max)) break; if (opp_table->regulator_count > 1) { if (volt > volt_mem_max) break; else if (volt < volt_mem_min) volt_mem = volt_mem_min; else volt_mem = volt; if (rockchip_pvtpll_set_volt(info->dev, reg_mem, volt_mem, volt_mem_max, "mem")) break; } if (rockchip_pvtpll_set_volt(info->dev, reg, volt, volt_max, "vdd")) break; pvtpll_rate = rockchip_pvtpll_get_rate(info); if (!pvtpll_rate) goto out; delta1 = abs(pvtpll_rate - rate); } while (delta1 < delta0); volt -= cur_step; info->opp_table[i].u_volt = volt; if (opp_table->regulator_count > 1) { if (volt < volt_mem_min) volt_mem = volt_mem_min; else volt_mem = volt; info->opp_table[i].u_volt_mem = volt_mem; } } i = 0; mutex_lock(&opp_table->lock); list_for_each_entry(opp, &opp_table->opp_list, node) { if (!opp->available) continue; opp->supplies[0].u_volt = info->opp_table[i].u_volt; if (opp_table->regulator_count > 1) opp->supplies[1].u_volt = info->opp_table[i].u_volt_mem; i++; } mutex_unlock(&opp_table->lock); dev_info(info->dev, "opp calibration done\n"); out: if (cur_rate > old_rate) rockchip_pvtpll_set_clk(info->dev, opp_table->clk, old_rate); if (opp_table->regulator_count > 1) rockchip_pvtpll_set_volt(info->dev, reg_mem, old_volt_mem, INT_MAX, "mem"); rockchip_pvtpll_set_volt(info->dev, reg, old_volt, INT_MAX, "vdd"); if (cur_rate < old_rate) rockchip_pvtpll_set_clk(info->dev, opp_table->clk, old_rate); out_put: if (info->clocks) clk_bulk_disable_unprepare(info->nclocks, info->clocks); dev_pm_opp_put_opp_table(opp_table); } static void rockchip_pvtpll_add_length(struct rockchip_opp_info *info) { struct device_node *np; struct opp_table *opp_table; struct dev_pm_opp *opp; unsigned long old_rate; unsigned int min_rate = 0, max_rate = 0, margin = 0; u32 opp_flag = 0; int ret; if (!info) return; np = of_parse_phandle(info->dev->of_node, "operating-points-v2", 0); if (!np) { dev_warn(info->dev, "OPP-v2 not supported\n"); return; } if (of_property_read_u32(np, "rockchip,pvtpll-len-min-rate", &min_rate)) goto out; if (of_property_read_u32(np, "rockchip,pvtpll-len-max-rate", &max_rate)) goto out; if (of_property_read_u32(np, "rockchip,pvtpll-len-margin", &margin)) goto out; opp_table = dev_pm_opp_get_opp_table(info->dev); if (IS_ERR(opp_table)) goto out; old_rate = clk_get_rate(opp_table->clk); opp_flag = OPP_ADD_LENGTH | ((margin & OPP_LENGTH_MASK) << OPP_LENGTH_SHIFT); mutex_lock(&opp_table->lock); list_for_each_entry(opp, &opp_table->opp_list, node) { if (opp->rates[0] < min_rate * 1000 || opp->rates[0] > max_rate * 1000) continue; ret = clk_set_rate(opp_table->clk, opp->rates[0] | opp_flag); if (ret) { dev_err(info->dev, "failed to change %lu len margin %d\n", opp->rates[0], margin); break; } } mutex_unlock(&opp_table->lock); clk_set_rate(opp_table->clk, old_rate); dev_pm_opp_put_opp_table(opp_table); out: of_node_put(np); } static int rockchip_get_pvtm_pvtpll(struct device *dev, struct device_node *np, struct rockchip_opp_info *info, const char *reg_name) { struct regulator *reg; struct clk *clk; struct pvtm_config *pvtm; unsigned long old_freq; unsigned int old_volt; int cur_temp, diff_temp, prop_temp, diff_value; int pvtm_value = 0; int ret = 0; if (!rockchip_nvmem_cell_read_u16(np, "pvtm", (u16 *)&pvtm_value) && pvtm_value) { dev_info(dev, "pvtm = %d, get from otp\n", pvtm_value); return pvtm_value; } if (!info || !info->pvtpll_base) return -ENOMEM; pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL); if (!pvtm) return -ENOMEM; ret = rockchip_parse_pvtm_config(np, pvtm); if (ret) goto out; clk = clk_get(dev, NULL); if (IS_ERR_OR_NULL(clk)) { dev_warn(dev, "Failed to get clk\n"); goto out; } reg = regulator_get_optional(dev, reg_name); if (IS_ERR_OR_NULL(reg)) { dev_warn(dev, "Failed to get reg\n"); clk_put(clk); goto out; } old_freq = clk_get_rate(clk); old_volt = regulator_get_voltage(reg); ret = clk_set_rate(clk, pvtm->freq * 1000); if (ret) { dev_err(dev, "Failed to set pvtm freq\n"); goto put_reg; } ret = regulator_set_voltage(reg, pvtm->volt, INT_MAX); if (ret) { dev_err(dev, "Failed to set pvtm_volt\n"); goto restore_clk; } usleep_range(pvtm->sample_time, pvtm->sample_time + 100); ret = regmap_read(info->pvtpll_base, pvtm->offset, &pvtm_value); if (ret < 0) { dev_err(dev, "failed to get pvtm from 0x%x\n", pvtm->offset); goto resetore_volt; } pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); diff_temp = (cur_temp / 1000 - pvtm->ref_temp); if (diff_temp < 0) prop_temp = pvtm->temp_prop[0]; else prop_temp = pvtm->temp_prop[1]; diff_value = diff_temp * prop_temp / 1000; pvtm_value += diff_value; dev_info(dev, "pvtm=%d\n", pvtm_value); resetore_volt: regulator_set_voltage(reg, old_volt, INT_MAX); restore_clk: clk_set_rate(clk, old_freq); put_reg: regulator_put(reg); clk_put(clk); out: kfree(pvtm); return pvtm_value; } static int rockchip_get_pvtm(struct device *dev, struct device_node *np, const char *reg_name) { struct regulator *reg; struct clk *clk; unsigned int ch[2]; int pvtm = 0; u16 tmp = 0; if (!rockchip_nvmem_cell_read_u16(np, "pvtm", &tmp) && tmp) { pvtm = 10 * tmp; dev_info(dev, "pvtm = %d, from nvmem\n", pvtm); return pvtm; } if (of_property_read_u32_array(np, "rockchip,pvtm-ch", ch, 2)) return -EINVAL; if (ch[0] >= PVTM_CH_MAX || ch[1] >= PVTM_SUB_CH_MAX) return -EINVAL; if (pvtm_value[ch[0]][ch[1]]) { dev_info(dev, "pvtm = %d, form pvtm_value\n", pvtm_value[ch[0]][ch[1]]); return pvtm_value[ch[0]][ch[1]]; } clk = clk_get(dev, NULL); if (IS_ERR_OR_NULL(clk)) { dev_warn(dev, "Failed to get clk\n"); return PTR_ERR_OR_ZERO(clk); } reg = regulator_get_optional(dev, reg_name); if (IS_ERR_OR_NULL(reg)) { dev_warn(dev, "Failed to get reg\n"); clk_put(clk); return PTR_ERR_OR_ZERO(reg); } rockchip_get_pvtm_specific_value(dev, np, clk, reg, &pvtm); regulator_put(reg); clk_put(clk); return pvtm; } static void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np, struct rockchip_opp_info *info, const char *reg_name, int *volt_sel, int *scale_sel) { struct property *prop = NULL; char name[NAME_MAX]; int pvtm, ret; u32 hw = 0; if (of_property_read_bool(np, "rockchip,pvtm-pvtpll")) pvtm = rockchip_get_pvtm_pvtpll(dev, np, info, reg_name); else pvtm = rockchip_get_pvtm(dev, np, reg_name); if (pvtm <= 0) return; if (!volt_sel) goto next; if (info->process >= 0) { snprintf(name, sizeof(name), "rockchip,p%d-pvtm-voltage-sel", info->process); prop = of_find_property(np, name, NULL); } else if (info->bin > 0) { of_property_read_u32(np, "rockchip,pvtm-hw", &hw); if (hw && (hw & BIT(info->bin))) { sprintf(name, "rockchip,pvtm-voltage-sel-hw"); prop = of_find_property(np, name, NULL); } if (!prop) { snprintf(name, sizeof(name), "rockchip,pvtm-voltage-sel-B%d", info->bin); prop = of_find_property(np, name, NULL); } } if (!prop) sprintf(name, "rockchip,pvtm-voltage-sel"); ret = rockchip_get_sel(np, name, pvtm, volt_sel); if (!ret && volt_sel) dev_info(dev, "pvtm-volt-sel=%d\n", *volt_sel); next: if (!scale_sel) return; prop = NULL; if (info->process >= 0) { snprintf(name, sizeof(name), "rockchip,p%d-pvtm-scaling-sel", info->process); prop = of_find_property(np, name, NULL); } if (!prop) sprintf(name, "rockchip,pvtm-scaling-sel"); ret = rockchip_get_sel(np, name, pvtm, scale_sel); if (!ret) dev_info(dev, "pvtm-scale=%d\n", *scale_sel); } static void rockchip_of_get_bin_sel(struct device *dev, struct device_node *np, int bin, int *scale_sel) { int ret = 0; if (!scale_sel || bin < 0) return; ret = rockchip_get_bin_sel(np, "rockchip,bin-scaling-sel", bin, scale_sel); if (!ret) dev_info(dev, "bin-scale=%d\n", *scale_sel); } static void rockchip_of_get_bin_volt_sel(struct device *dev, struct device_node *np, int bin, int *bin_volt_sel) { int ret = 0; if (!bin_volt_sel || bin < 0) return; ret = rockchip_get_bin_sel(np, "rockchip,bin-voltage-sel", bin, bin_volt_sel); if (!ret) dev_info(dev, "bin-volt-sel=%d\n", *bin_volt_sel); } void rockchip_get_opp_data(const struct of_device_id *matches, struct rockchip_opp_info *info) { const struct of_device_id *match; struct device_node *node; node = of_find_node_by_path("/"); match = of_match_node(matches, node); if (match && match->data) info->data = match->data; of_node_put(node); } EXPORT_SYMBOL(rockchip_get_opp_data); static int rockchip_get_volt_rm_table(struct device *dev, struct device_node *np, char *porp_name, struct volt_rm_table **table) { struct volt_rm_table *rm_table; const struct property *prop; int count, i; prop = of_find_property(np, porp_name, NULL); if (!prop) return -EINVAL; if (!prop->value) return -ENODATA; count = of_property_count_u32_elems(np, porp_name); if (count < 0) return -EINVAL; if (count % 2) return -EINVAL; rm_table = devm_kzalloc(dev, sizeof(*rm_table) * (count / 2 + 1), GFP_KERNEL); if (!rm_table) return -ENOMEM; for (i = 0; i < count / 2; i++) { of_property_read_u32_index(np, porp_name, 2 * i, &rm_table[i].volt); of_property_read_u32_index(np, porp_name, 2 * i + 1, &rm_table[i].rm); } rm_table[i].volt = 0; rm_table[i].rm = VOLT_RM_TABLE_END; *table = rm_table; return 0; } static int rockchip_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { u8 value = 0; int ret = 0; if (*bin >= 0 || *process >= 0) return 0; if (of_property_match_string(np, "nvmem-cell-names", "remark_spec_serial_number") >= 0) rockchip_nvmem_cell_read_u8(np, "remark_spec_serial_number", &value); if (!value && of_property_match_string(np, "nvmem-cell-names", "specification_serial_number") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "specification_serial_number", &value); if (ret) { dev_err(dev, "Failed to get specification_serial_number\n"); return ret; } } /* M */ if (value == 0xd) *bin = 1; /* J */ else if (value == 0xa) *bin = 2; /* S */ else if (value == 0x13) *bin = 3; if (*bin < 0) *bin = 0; dev_info(dev, "bin=%d\n", *bin); return 0; } static void rockchip_init_pvtpll_table(struct device *dev, struct rockchip_opp_info *info) { struct device_node *np = NULL; struct property *prop = NULL; struct of_phandle_args clkspec = { 0 }; struct arm_smccc_res res; char prop_name[NAME_MAX]; u32 *value; int count; int ret, i; if (!info) return; np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); if (!np) { dev_warn(dev, "OPP-v2 not supported\n"); return; } ret = of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells", 0, &clkspec); if (ret) goto out; info->pvtpll_clk_id = clkspec.args[0]; of_node_put(clkspec.np); res = sip_smc_get_pvtpll_info(PVTPLL_GET_INFO, info->pvtpll_clk_id); if (res.a0) { info->pvtpll_smc = false; goto out; } if (!res.a1) info->pvtpll_low_temp = true; if (info->bin > 0) { snprintf(prop_name, sizeof(prop_name), "rockchip,pvtpll-table-B%d", info->bin); prop = of_find_property(np, prop_name, NULL); } if (!prop) sprintf(prop_name, "rockchip,pvtpll-table"); prop = of_find_property(np, prop_name, NULL); if (!prop) goto out; count = of_property_count_u32_elems(np, prop_name); if (count < 0) { dev_err(dev, "%s: Invalid %s property (%d)\n", __func__, prop_name, count); goto out; } else if (count % 5) { dev_err(dev, "Invalid count of %s\n", prop_name); goto out; } value = kmalloc_array(count, sizeof(*value), GFP_KERNEL); if (!value) goto out; ret = of_property_read_u32_array(np, prop_name, value, count); if (ret) { dev_err(dev, "%s: error parsing %s: %d\n", __func__, prop_name, ret); goto free_value; } for (i = 0; i < count; i += 5) { res = sip_smc_pvtpll_config(PVTPLL_ADJUST_TABLE, info->pvtpll_clk_id, value[i], value[i + 1], value[i + 2], value[i + 3], value[i + 4]); if (res.a0) { dev_err(dev, "%s: error cfg clk_id=%u %u %u %u %u %u (%d)\n", __func__, info->pvtpll_clk_id, value[i], value[i + 1], value[i + 2], value[i + 3], value[i + 4], (int)res.a0); goto free_value; } } free_value: kfree(value); out: of_node_put(np); } static int rockchip_set_opp_supported_hw(struct device *dev, struct device_node *np, struct rockchip_opp_info *info) { u32 version = 0, speed = 0; if (!of_property_read_bool(np, "rockchip,supported-hw")) return 0; if (info->supported_hw[0] || info->supported_hw[1]) return 0; if (info->bin >= 0) version = info->bin; if (info->volt_sel >= 0) speed = info->volt_sel; /* SoC Version */ info->supported_hw[0] = BIT(version); /* Speed Grade */ info->supported_hw[1] = BIT(speed); dev_info(dev, "soc version=%d, speed=%d\n", version, speed); return 0; } static void rockchip_get_scale_volt_sel(struct device *dev, char *lkg_name, const char *reg_name, struct rockchip_opp_info *info) { struct device_node *np; int lkg_scale = 0, pvtm_scale = 0, bin_scale = 0; int lkg_volt_sel = -EINVAL, pvtm_volt_sel = -EINVAL; int bin_volt_sel = -EINVAL; np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); if (!np) { dev_warn(dev, "OPP-v2 not supported\n"); return; } rockchip_of_get_lkg_sel(dev, np, lkg_name, info->process, &lkg_volt_sel, &lkg_scale); rockchip_of_get_pvtm_sel(dev, np, info, reg_name, &pvtm_volt_sel, &pvtm_scale); rockchip_of_get_bin_sel(dev, np, info->bin, &bin_scale); rockchip_of_get_bin_volt_sel(dev, np, info->bin, &bin_volt_sel); info->scale = max3(lkg_scale, pvtm_scale, bin_scale); if (bin_volt_sel >= 0) info->volt_sel = bin_volt_sel; else info->volt_sel = max(lkg_volt_sel, pvtm_volt_sel); of_node_put(np); } static int rockchip_opp_set_regulator_helper(struct device *dev, struct rockchip_opp_info *info) { struct opp_table *opp_table; if (!info || !info->data || !info->data->config_regulators) return 0; opp_table = dev_pm_opp_get_opp_table(dev); if (IS_ERR(opp_table)) return PTR_ERR(opp_table); opp_table->config_regulators = info->data->config_regulators; dev_pm_opp_put_opp_table(opp_table); return 0; } static int rockchip_opp_set_config(struct device *dev, struct rockchip_opp_info *info, const char *clk_name, const char *reg_name) { char name[MAX_PROP_NAME_LEN]; struct dev_pm_opp_config config = {0}; struct clk *clk = NULL; const char *reg_names[] = {NULL, NULL, NULL}; const char *clk_names[] = {NULL, NULL, NULL}; if (clk_name) { clk = clk_get(dev, clk_name); if (IS_ERR_OR_NULL(clk)) { if (!of_property_read_string_index(dev->of_node, "clock-names", 0, &clk_name)) clk = clk_get(dev, clk_name); } if (!IS_ERR_OR_NULL(clk)) { if (strstr(__clk_get_name(clk), "scmi")) info->is_scmi_clk = true; clk_names[0] = clk_name; config.clk_names = clk_names; clk_put(clk); if (info->data && info->data->config_clks) config.config_clks = info->data->config_clks; } } if (info->process >= 0) { if (info->volt_sel >= 0) snprintf(name, MAX_PROP_NAME_LEN, "P%d-L%d", info->process, info->volt_sel); else snprintf(name, MAX_PROP_NAME_LEN, "P%d", info->process); config.prop_name = name; } else if (info->volt_sel >= 0) { snprintf(name, MAX_PROP_NAME_LEN, "L%d", info->volt_sel); config.prop_name = name; } if (info->data && info->data->config_regulators) config.config_regulators = info->data->config_regulators; if (info->supported_hw[0] || info->supported_hw[1]) { config.supported_hw = kmemdup(info->supported_hw, 2 * sizeof(u32), GFP_KERNEL); config.supported_hw_count = 2; } if (reg_name) { reg_names[0] = reg_name; if (of_find_property(dev->of_node, "mem-supply", NULL)) reg_names[1] = "mem"; config.regulator_names = reg_names; } info->opp_token = dev_pm_opp_set_config(dev, &config); if (info->opp_token < 0) { dev_err(dev, "failed to set opp config\n"); return info->opp_token; } /* * The dev_pm_opp_set_config() only support setting regulator helper * for multiple regulators, but on some platforms, still need to set * regulator helper for single regulator. */ if (rockchip_opp_set_regulator_helper(dev, info)) { dev_err(dev, "failed to set opp regulator helper\n"); return -EINVAL; } return 0; } void rockchip_opp_dvfs_lock(struct rockchip_opp_info *info) { if (info) mutex_lock(&info->dvfs_mutex); } EXPORT_SYMBOL(rockchip_opp_dvfs_lock); void rockchip_opp_dvfs_unlock(struct rockchip_opp_info *info) { if (info) mutex_unlock(&info->dvfs_mutex); } EXPORT_SYMBOL(rockchip_opp_dvfs_unlock); static int rockchip_get_opp_clk(struct device *dev, struct device_node *np, struct rockchip_opp_info *info) { struct clk_bulk_data *clocks; struct of_phandle_args clkspec; int ret = 0, nclocks = 0, i; if (of_find_property(np, "rockchip,opp-clocks", NULL)) { nclocks = of_count_phandle_with_args(np, "rockchip,opp-clocks", "#clock-cells"); if (nclocks <= 0) return 0; clocks = devm_kcalloc(dev, nclocks, sizeof(*clocks), GFP_KERNEL); if (!clocks) return -ENOMEM; for (i = 0; i < nclocks; i++) { ret = of_parse_phandle_with_args(np, "rockchip,opp-clocks", "#clock-cells", i, &clkspec); if (ret < 0) { dev_err(dev, "%s: failed to parse opp clk %d\n", np->name, i); goto error; } clocks[i].clk = of_clk_get_from_provider(&clkspec); of_node_put(clkspec.np); if (IS_ERR(clocks[i].clk)) { ret = PTR_ERR(clocks[i].clk); clocks[i].clk = NULL; dev_err(dev, "%s: failed to get opp clk %d\n", np->name, i); goto error; } } } else { nclocks = of_clk_get_parent_count(np); if (nclocks <= 0) return 0; clocks = devm_kcalloc(dev, nclocks, sizeof(*clocks), GFP_KERNEL); if (!clocks) return -ENOMEM; for (i = 0; i < nclocks; i++) { clocks[i].clk = of_clk_get(np, i); if (IS_ERR(clocks[i].clk)) { ret = PTR_ERR(clocks[i].clk); clocks[i].clk = NULL; dev_err(dev, "%s: failed to get clk %d\n", np->name, i); goto error; } } } info->clocks = clocks; info->nclocks = nclocks; return 0; error: while (--i >= 0) clk_put(clocks[i].clk); devm_kfree(dev, clocks); return ret; } int rockchip_init_opp_info(struct device *dev, struct rockchip_opp_info *info, char *clk_name, char *reg_name) { struct device_node *np; int ret = 0; u32 freq; /* Get OPP descriptor node */ np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); if (!np) { dev_dbg(dev, "Failed to find operating-points-v2\n"); return -ENOENT; } if (!info) return -ENOMEM; info->dev = dev; info->bin = -EINVAL; info->process = -EINVAL; info->volt_sel = -EINVAL; info->pvtpll_clk_id = UINT_MAX; info->pvtpll_smc = true; info->is_runtime_active = true; mutex_init(&info->dvfs_mutex); of_property_read_u32(np, "rockchip,init-freq", &info->init_freq); info->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); if (IS_ERR(info->grf)) info->grf = NULL; info->dsu_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,dsu-grf"); if (IS_ERR(info->dsu_grf)) { info->dsu_grf = NULL; info->cci_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,cci-grf"); if (IS_ERR(info->cci_grf)) info->cci_grf = NULL; } info->pvtpll_base = syscon_regmap_lookup_by_phandle(np, "rockchip,pvtpll"); if (IS_ERR(info->pvtpll_base)) info->pvtpll_base = info->grf; ret = rockchip_get_opp_clk(dev, np, info); if (ret) goto out; if (info->clocks) { ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); if (ret) { dev_err(dev, "failed to enable opp clks\n"); goto out; } } if (info->data && info->data->set_read_margin) { info->current_rm = UINT_MAX; info->target_rm = UINT_MAX; rockchip_get_volt_rm_table(dev, np, "volt-mem-read-margin", &info->volt_rm_tbl); of_property_read_u32(np, "low-volt-mem-read-margin", &info->low_rm); if (!of_property_read_u32(np, "intermediate-threshold-freq", &freq)) info->intermediate_threshold_freq = freq * 1000; rockchip_init_read_margin(dev, info, reg_name); } if (info->data && info->data->get_soc_info) info->data->get_soc_info(dev, np, &info->bin, &info->process); rockchip_get_soc_info(dev, np, &info->bin, &info->process); rockchip_init_pvtpll_table(dev, info); rockchip_get_scale_volt_sel(dev, "leakage", reg_name, info); if (info && info->data && info->data->set_soc_info) info->data->set_soc_info(dev, np, info); rockchip_set_opp_supported_hw(dev, np, info); ret = rockchip_opp_set_config(dev, info, clk_name, reg_name); if (info->clocks) clk_bulk_disable_unprepare(info->nclocks, info->clocks); out: of_node_put(np); return ret; } EXPORT_SYMBOL(rockchip_init_opp_info); void rockchip_uninit_opp_info(struct device *dev, struct rockchip_opp_info *info) { dev_pm_opp_clear_config(info->opp_token); } EXPORT_SYMBOL(rockchip_uninit_opp_info); static int rockchip_adjust_opp_by_irdrop(struct device *dev, struct device_node *np, unsigned long *safe_rate, unsigned long *max_rate) { struct sel_table *irdrop_table = NULL; struct opp_table *opp_table; struct dev_pm_opp *opp; unsigned long tmp_safe_rate = 0; int evb_irdrop = 0, board_irdrop, delta_irdrop; int opp_rate, i, ret = 0; u32 max_volt = UINT_MAX; bool reach_max_volt = false; of_property_read_u32_index(np, "rockchip,max-volt", 0, &max_volt); of_property_read_u32_index(np, "rockchip,evb-irdrop", 0, &evb_irdrop); rockchip_get_sel_table(np, "rockchip,board-irdrop", &irdrop_table); opp_table = dev_pm_opp_get_opp_table(dev); if (IS_ERR(opp_table)) { ret = PTR_ERR(opp_table); goto out; } mutex_lock(&opp_table->lock); list_for_each_entry(opp, &opp_table->opp_list, node) { if (!opp->available) continue; if (!irdrop_table) { delta_irdrop = 0; } else { opp_rate = opp->rates[0] / 1000000; board_irdrop = -EINVAL; for (i = 0; irdrop_table[i].sel != SEL_TABLE_END; i++) { if (opp_rate >= irdrop_table[i].min) board_irdrop = irdrop_table[i].sel; } if (board_irdrop == -EINVAL) delta_irdrop = 0; else delta_irdrop = board_irdrop - evb_irdrop; } if ((opp->supplies[0].u_volt + delta_irdrop) <= max_volt) { opp->supplies[0].u_volt += delta_irdrop; opp->supplies[0].u_volt_min += delta_irdrop; if (opp->supplies[0].u_volt_max + delta_irdrop <= max_volt) opp->supplies[0].u_volt_max += delta_irdrop; else opp->supplies[0].u_volt_max = max_volt; if (!reach_max_volt) tmp_safe_rate = opp->rates[0]; if (opp->supplies[0].u_volt == max_volt) reach_max_volt = true; } else { opp->supplies[0].u_volt = max_volt; opp->supplies[0].u_volt_min = max_volt; opp->supplies[0].u_volt_max = max_volt; } if (max_rate) *max_rate = opp->rates[0]; if (safe_rate && tmp_safe_rate != opp->rates[0]) *safe_rate = tmp_safe_rate; } mutex_unlock(&opp_table->lock); dev_pm_opp_put_opp_table(opp_table); out: kfree(irdrop_table); return ret; } static void rockchip_adjust_opp_by_mbist_vmin(struct device *dev, struct device_node *np) { struct opp_table *opp_table; struct dev_pm_opp *opp; u32 vmin = 0; u8 index = 0; if (rockchip_nvmem_cell_read_u8(np, "mbist-vmin", &index)) return; if (!index) return; if (of_property_read_u32_index(np, "mbist-vmin", index-1, &vmin)) return; opp_table = dev_pm_opp_get_opp_table(dev); if (IS_ERR(opp_table)) return; mutex_lock(&opp_table->lock); list_for_each_entry(opp, &opp_table->opp_list, node) { if (!opp->available) continue; if (opp->supplies->u_volt < vmin) { opp->supplies->u_volt = vmin; opp->supplies->u_volt_min = vmin; } } mutex_unlock(&opp_table->lock); } static void rockchip_adjust_opp_by_otp(struct device *dev, struct device_node *np) { struct dev_pm_opp *opp; struct opp_table *opp_table; struct otp_opp_info opp_info = {}; int ret; ret = rockchip_nvmem_cell_read_common(np, "opp-info", &opp_info, sizeof(opp_info)); if (ret || !opp_info.volt) return; dev_info(dev, "adjust opp-table by otp: min=%uM, max=%uM, volt=%umV\n", opp_info.min_freq, opp_info.max_freq, opp_info.volt); opp_table = dev_pm_opp_get_opp_table(dev); if (IS_ERR(opp_table)) return; mutex_lock(&opp_table->lock); list_for_each_entry(opp, &opp_table->opp_list, node) { if (!opp->available) continue; if (opp->rates[0] < opp_info.min_freq * 1000000) continue; if (opp->rates[0] > opp_info.max_freq * 1000000) continue; opp->supplies[0].u_volt += opp_info.volt * 1000; if (opp->supplies[0].u_volt > opp->supplies[0].u_volt_max) opp->supplies[0].u_volt = opp->supplies[0].u_volt_max; if (opp_table->regulator_count > 1) { opp->supplies[1].u_volt += opp_info.volt * 1000; if (opp->supplies[1].u_volt > opp->supplies[1].u_volt_max) opp->supplies[1].u_volt = opp->supplies[1].u_volt_max; } } mutex_unlock(&opp_table->lock); dev_pm_opp_put_opp_table(opp_table); } static int rockchip_adjust_opp_by_scale(struct device *dev, unsigned long scale_rate) { struct dev_pm_opp *opp; unsigned long rate; int i, count, ret = 0; count = dev_pm_opp_get_opp_count(dev); if (count <= 0) { ret = count ? count : -ENODATA; goto out; } for (i = 0, rate = 0; i < count; i++, rate++) { /* find next rate */ opp = dev_pm_opp_find_freq_ceil(dev, &rate); if (IS_ERR(opp)) { ret = PTR_ERR(opp); goto out; } if (opp->rates[0] > scale_rate) dev_pm_opp_disable(dev, opp->rates[0]); dev_pm_opp_put(opp); } out: return ret; } static int rockchip_adjust_power_scale(struct device *dev, struct rockchip_opp_info *info) { struct device_node *np; struct clk *clk; unsigned long safe_rate = 0, max_rate = 0; int irdrop_scale = 0, opp_scale = 0; u32 target_scale, avs = 0, avs_scale = 0; long scale_rate = 0; int ret = 0; np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); if (!np) { dev_warn(dev, "OPP-v2 not supported\n"); return -ENOENT; } of_property_read_u32(np, "rockchip,avs-enable", &avs); of_property_read_u32(np, "rockchip,avs", &avs); of_property_read_u32(np, "rockchip,avs-scale", &avs_scale); rockchip_adjust_opp_by_otp(dev, np); rockchip_adjust_opp_by_mbist_vmin(dev, np); rockchip_adjust_opp_by_irdrop(dev, np, &safe_rate, &max_rate); dev_info(dev, "avs=%d\n", avs); if (!safe_rate && !info->scale) goto out_np; clk = of_clk_get_by_name(np, NULL); if (IS_ERR(clk)) { if (!safe_rate) goto out_np; dev_dbg(dev, "Failed to get clk, safe_rate=%lu\n", safe_rate); ret = rockchip_adjust_opp_by_scale(dev, safe_rate); if (ret) dev_err(dev, "Failed to adjust opp table\n"); goto out_np; } if (safe_rate) irdrop_scale = rockchip_pll_clk_rate_to_scale(clk, safe_rate); target_scale = max(irdrop_scale, info->scale); if (target_scale <= 0) goto out_clk; dev_dbg(dev, "target_scale=%d, irdrop_scale=%d, scale=%d\n", target_scale, irdrop_scale, info->scale); if (max_rate) opp_scale = rockchip_pll_clk_rate_to_scale(clk, max_rate); if (avs == AVS_SCALING_RATE) { ret = rockchip_pll_clk_adaptive_scaling(clk, target_scale); if (ret) dev_err(dev, "Failed to adaptive scaling\n"); if (opp_scale >= avs_scale) goto out_clk; dev_info(dev, "avs-scale=%d, opp-scale=%d\n", avs_scale, opp_scale); scale_rate = rockchip_pll_clk_scale_to_rate(clk, avs_scale); if (scale_rate <= 0) { dev_err(dev, "Failed to get avs scale rate, %d\n", avs_scale); goto out_clk; } dev_dbg(dev, "scale_rate=%lu\n", scale_rate); ret = rockchip_adjust_opp_by_scale(dev, scale_rate); if (ret) dev_err(dev, "Failed to adjust opp table\n"); } else if (avs == AVS_DELETE_OPP) { if (opp_scale >= target_scale) goto out_clk; dev_info(dev, "target_scale=%d, opp-scale=%d\n", target_scale, opp_scale); scale_rate = rockchip_pll_clk_scale_to_rate(clk, target_scale); if (scale_rate <= 0) { dev_err(dev, "Failed to get scale rate, %d\n", target_scale); goto out_clk; } dev_dbg(dev, "scale_rate=%lu\n", scale_rate); ret = rockchip_adjust_opp_by_scale(dev, scale_rate); if (ret) dev_err(dev, "Failed to adjust opp table\n"); } out_clk: clk_put(clk); out_np: of_node_put(np); return ret; } static int rockchip_opp_parse_supplies(struct device *dev, struct rockchip_opp_info *info) { struct opp_table *opp_table; opp_table = dev_pm_opp_get_opp_table(dev); if (IS_ERR(opp_table)) return PTR_ERR(opp_table); if (opp_table->clk) info->clk = opp_table->clk; if (opp_table->regulators) info->regulators = opp_table->regulators; info->regulator_count = opp_table->regulator_count; dev_pm_opp_put_opp_table(opp_table); return 0; } static int rockchip_pvtpll_set_volt_sel(struct device *dev, struct rockchip_opp_info *info) { struct arm_smccc_res res; if (!info) return 0; if (info->volt_sel < 0) return 0; if (info->pvtpll_clk_id == UINT_MAX) return 0; if (!info->pvtpll_smc) return rockchip_pvtpll_volt_sel_adjust(info->pvtpll_clk_id, info->volt_sel); res = sip_smc_pvtpll_config(PVTPLL_VOLT_SEL, info->pvtpll_clk_id, (u32)info->volt_sel, 0, 0, 0, 0); if (res.a0) dev_err(dev, "%s: error cfg clk_id=%u voltsel (%d)\n", __func__, info->pvtpll_clk_id, (int)res.a0); return 0; } int rockchip_adjust_opp_table(struct device *dev, struct rockchip_opp_info *info) { rockchip_opp_parse_supplies(dev, info); rockchip_adjust_power_scale(dev, info); rockchip_pvtpll_calibrate_opp(info); rockchip_pvtpll_add_length(info); rockchip_pvtpll_set_volt_sel(dev, info); return 0; } EXPORT_SYMBOL(rockchip_adjust_opp_table); int rockchip_get_read_margin(struct device *dev, struct rockchip_opp_info *opp_info, unsigned long volt, u32 *target_rm) { int i; if (!opp_info || !opp_info->volt_rm_tbl) return 0; for (i = 0; opp_info->volt_rm_tbl[i].rm != VOLT_RM_TABLE_END; i++) { if (volt >= opp_info->volt_rm_tbl[i].volt) { opp_info->target_rm = opp_info->volt_rm_tbl[i].rm; break; } } *target_rm = opp_info->target_rm; return 0; } EXPORT_SYMBOL(rockchip_get_read_margin); int rockchip_set_read_margin(struct device *dev, struct rockchip_opp_info *opp_info, u32 rm, bool is_set_rm) { if (!is_set_rm || !opp_info) return 0; if (!opp_info || !opp_info->volt_rm_tbl) return 0; if (!opp_info->data || !opp_info->data->set_read_margin) return 0; if (rm == opp_info->current_rm) return 0; return opp_info->data->set_read_margin(dev, opp_info, rm); } EXPORT_SYMBOL(rockchip_set_read_margin); static int rockchip_init_read_margin(struct device *dev, struct rockchip_opp_info *opp_info, const char *reg_name) { struct clk *clk; struct regulator *reg; unsigned long cur_rate; int cur_volt, ret = 0; u32 target_rm = UINT_MAX; reg = regulator_get_optional(dev, reg_name); if (IS_ERR(reg)) { ret = PTR_ERR(reg); if (ret != -EPROBE_DEFER) dev_err(dev, "%s: no regulator (%s) found: %d\n", __func__, reg_name, ret); return ret; } cur_volt = regulator_get_voltage(reg); if (cur_volt < 0) { ret = cur_volt; if (ret != -EPROBE_DEFER) dev_err(dev, "%s: failed to get (%s) volt: %d\n", __func__, reg_name, ret); goto out; } clk = clk_get(dev, NULL); if (IS_ERR(clk)) { ret = PTR_ERR(clk); dev_err(dev, "%s: failed to get clk: %d\n", __func__, ret); goto out; } cur_rate = clk_get_rate(clk); rockchip_get_read_margin(dev, opp_info, cur_volt, &target_rm); dev_dbg(dev, "cur_rate=%lu, threshold=%lu, cur_volt=%d, target_rm=%d\n", cur_rate, opp_info->intermediate_threshold_freq, cur_volt, target_rm); if (opp_info->intermediate_threshold_freq && cur_rate > opp_info->intermediate_threshold_freq) { clk_set_rate(clk, opp_info->intermediate_threshold_freq); rockchip_set_read_margin(dev, opp_info, target_rm, true); clk_set_rate(clk, cur_rate); } else { rockchip_set_read_margin(dev, opp_info, target_rm, true); } clk_put(clk); out: regulator_put(reg); return ret; } int rockchip_set_intermediate_rate(struct device *dev, struct rockchip_opp_info *opp_info, struct clk *clk, unsigned long old_freq, unsigned long new_freq, bool is_scaling_up, bool is_set_clk) { if (!is_set_clk) return 0; if (!opp_info || !opp_info->volt_rm_tbl) return 0; if (!opp_info->data || !opp_info->data->set_read_margin) return 0; if (opp_info->target_rm == opp_info->current_rm) return 0; /* * There is no need to set intermediate rate if the new voltage * and the current voltage are high voltage. */ if ((opp_info->target_rm < opp_info->low_rm) && (opp_info->current_rm < opp_info->low_rm)) return 0; if (is_scaling_up) { /* * If scaling up and the current frequency is less than * or equal to intermediate threshold frequency, there is * no need to set intermediate rate. */ if (opp_info->intermediate_threshold_freq && old_freq <= opp_info->intermediate_threshold_freq) return 0; return clk_set_rate(clk, new_freq | OPP_SCALING_UP_INTER); } /* * If scaling down and the new frequency is less than or equal to * intermediate threshold frequency , there is no need to set * intermediate rate and set the new frequency directly. */ if (opp_info->intermediate_threshold_freq && new_freq <= opp_info->intermediate_threshold_freq) return clk_set_rate(clk, new_freq); return clk_set_rate(clk, new_freq | OPP_SCALING_DOWN_INTER); } EXPORT_SYMBOL(rockchip_set_intermediate_rate); int rockchip_opp_set_low_length(struct device *dev, struct device_node *np, struct rockchip_opp_info *opp_info) { struct clk *clk; unsigned long old_rate; unsigned int low_len_sel; u32 opp_flag = 0; int ret = 0; if (opp_info->volt_sel < 0) return 0; clk = clk_get(dev, NULL); if (IS_ERR(clk)) { dev_warn(dev, "failed to get cpu clk\n"); return PTR_ERR(clk); } /* low speed grade should change to low length */ if (of_property_read_u32(np, "rockchip,pvtm-low-len-sel", &low_len_sel)) goto out; if (opp_info->volt_sel > low_len_sel) goto out; opp_flag = OPP_LENGTH_LOW; old_rate = clk_get_rate(clk); ret = clk_set_rate(clk, old_rate | opp_flag); if (ret) { dev_err(dev, "failed to change length\n"); goto out; } clk_set_rate(clk, old_rate); out: clk_put(clk); return ret; } EXPORT_SYMBOL(rockchip_opp_set_low_length); static int rockchip_opp_set_volt(struct device *dev, struct regulator *reg, struct dev_pm_opp_supply *supply, char *reg_name) { int ret = 0; ret = regulator_set_voltage_triplet(reg, supply->u_volt_min, supply->u_volt, supply->u_volt_max); if (ret) dev_err(dev, "%s: failed to set voltage (%lu %lu %lu uV): %d\n", reg_name, supply->u_volt_min, supply->u_volt, supply->u_volt_max, ret); return ret; } int rockchip_opp_config_regulators(struct device *dev, struct dev_pm_opp *old_opp, struct dev_pm_opp *new_opp, struct regulator **regulators, unsigned int count, struct rockchip_opp_info *info) { struct regulator *vdd_reg = regulators[0]; struct regulator *mem_reg; struct dev_pm_opp_supply old_supplies[2] = { 0 }; struct dev_pm_opp_supply new_supplies[2] = { 0 }; unsigned long old_freq, freq; u32 target_rm = UINT_MAX; int ret = 0; if (count > 1) mem_reg = regulators[1]; ret = dev_pm_opp_get_supplies(new_opp, new_supplies); if (ret) return ret; ret = dev_pm_opp_get_supplies(old_opp, old_supplies); if (ret) return ret; ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); if (ret) { dev_err(dev, "failed to enable opp clks\n"); return ret; } rockchip_get_read_margin(dev, info, new_supplies[0].u_volt, &target_rm); old_freq = dev_pm_opp_get_freq(old_opp); freq = dev_pm_opp_get_freq(new_opp); if (count > 1) dev_dbg(dev, "%lu %lu -> %lu %lu (uV)\n", old_supplies[0].u_volt, old_supplies[1].u_volt, new_supplies[0].u_volt, new_supplies[1].u_volt); else dev_dbg(dev, "%lu -> %lu (uV)\n", old_supplies[0].u_volt, new_supplies[0].u_volt); if (freq > old_freq) { if (count > 1) { ret = rockchip_opp_set_volt(dev, mem_reg, &new_supplies[1], "mem"); if (ret) goto restore_voltage; } ret = rockchip_opp_set_volt(dev, vdd_reg, &new_supplies[0], "vdd"); if (ret) goto restore_voltage; rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active); } else { rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active); ret = rockchip_opp_set_volt(dev, vdd_reg, &new_supplies[0], "vdd"); if (ret) goto restore_voltage; if (count > 1) { ret = rockchip_opp_set_volt(dev, mem_reg, &new_supplies[1], "mem"); if (ret) goto restore_voltage; } } clk_bulk_disable_unprepare(info->nclocks, info->clocks); return 0; restore_voltage: rockchip_get_read_margin(dev, info, old_supplies[0].u_volt, &target_rm); rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active); if (old_supplies[0].u_volt) { if (count > 1 && old_supplies[1].u_volt) { ret = rockchip_opp_set_volt(dev, mem_reg, &old_supplies[1], "mem"); if (ret) goto dis_clks; } ret = rockchip_opp_set_volt(dev, vdd_reg, &old_supplies[0], "vdd"); if (ret) goto dis_clks; } dis_clks: clk_bulk_disable_unprepare(info->nclocks, info->clocks); return ret; } EXPORT_SYMBOL(rockchip_opp_config_regulators); int rockchip_opp_config_clks(struct device *dev, struct opp_table *opp_table, struct dev_pm_opp *opp, void *data, bool scaling_down, struct rockchip_opp_info *info) { unsigned long *target = data; int ret; if (info->is_scmi_clk && !info->is_runtime_active) return 0; ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); if (ret) { dev_err(dev, "failed to enable opp clks\n"); return ret; } dev_dbg(dev, "%lu -> %lu (Hz)\n", opp_table->rate_clk_single, *target); ret = clk_set_rate(opp_table->clk, *target); if (ret) dev_err(dev, "failed to set clock rate: %lu\n", *target); else opp_table->rate_clk_single = *target; clk_bulk_disable_unprepare(info->nclocks, info->clocks); return ret; } EXPORT_SYMBOL(rockchip_opp_config_clks); int rockchip_opp_check_rate_volt(struct device *dev, struct rockchip_opp_info *info) { struct regulator *vdd_reg = NULL; struct regulator *mem_reg = NULL; struct dev_pm_opp *opp; unsigned long old_rate = 0, new_rate = 0; unsigned long new_volt = 0, new_volt_mem = 0; int old_volt = 0, old_volt_mem = 0; u32 target_rm = UINT_MAX; bool is_set_clk = true; bool is_set_rm = false; int ret = 0; if (!info->regulators || !info->clk) return 0; vdd_reg = info->regulators[0]; old_rate = clk_get_rate(info->clk); old_volt = regulator_get_voltage(vdd_reg); if (info->regulator_count > 1) { mem_reg = info->regulators[1]; old_volt_mem = regulator_get_voltage(mem_reg); } if (info->init_freq) { new_rate = info->init_freq * 1000; info->init_freq = 0; } else { new_rate = old_rate; } opp = dev_pm_opp_find_freq_ceil(dev, &new_rate); if (IS_ERR(opp)) { opp = dev_pm_opp_find_freq_floor(dev, &new_rate); if (IS_ERR(opp)) return PTR_ERR(opp); } new_volt = opp->supplies[0].u_volt; if (info->regulator_count > 1) new_volt_mem = opp->supplies[1].u_volt; dev_pm_opp_put(opp); if (old_rate == new_rate && info->is_rate_volt_checked) { if (info->regulator_count > 1) { if (old_volt == new_volt && new_volt_mem == old_volt_mem) return 0; } else if (old_volt == new_volt) { return 0; } } if (!new_volt || (info->regulator_count > 1 && !new_volt_mem)) return 0; ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); if (ret) { dev_err(dev, "failed to enable opp clks\n"); return ret; } if (info->is_scmi_clk && !info->is_runtime_active) is_set_clk = false; if (info->data && info->data->set_read_margin && info->is_runtime_active) is_set_rm = true; rockchip_get_read_margin(dev, info, new_volt, &target_rm); dev_dbg(dev, "%s: %lu Hz --> %lu Hz, %lu %lu uV\n", __func__, old_rate, new_rate, new_volt, new_volt_mem); if (new_rate >= old_rate) { if (old_volt > new_volt) { ret = regulator_set_voltage(vdd_reg, new_volt, INT_MAX); if (ret) { dev_err(dev, "%s: failed to set volt: %lu\n", __func__, new_volt); goto restore_voltage; } } if (info->regulator_count > 1) { ret = regulator_set_voltage(mem_reg, new_volt_mem, INT_MAX); if (ret) { dev_err(dev, "%s: failed to set volt: %lu\n", __func__, new_volt_mem); goto disable_clk; } } if (old_volt <= new_volt) { ret = regulator_set_voltage(vdd_reg, new_volt, INT_MAX); if (ret) { dev_err(dev, "%s: failed to set volt: %lu\n", __func__, new_volt); goto restore_voltage; } } rockchip_set_read_margin(dev, info, target_rm, is_set_rm); if (new_rate == old_rate) goto out; } if (is_set_clk) { ret = clk_set_rate(info->clk, new_rate); if (ret) { dev_err(dev, "%s: failed to set clock rate: %lu\n", __func__, new_rate); goto restore_rm; } } if (new_rate < old_rate) { rockchip_set_read_margin(dev, info, target_rm, is_set_rm); ret = regulator_set_voltage(vdd_reg, new_volt, INT_MAX); if (ret) { dev_err(dev, "%s: failed to set volt: %lu\n", __func__, new_volt); goto restore_freq; } if (info->regulator_count > 1) { ret = regulator_set_voltage(mem_reg, new_volt_mem, INT_MAX); if (ret) { dev_err(dev, "%s: failed to set volt: %lu\n", __func__, new_volt_mem); goto restore_freq; } } } out: clk_bulk_disable_unprepare(info->nclocks, info->clocks); return 0; restore_freq: if (is_set_clk && clk_set_rate(info->clk, old_rate)) dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", __func__, old_rate); restore_rm: rockchip_get_read_margin(dev, info, old_volt, &target_rm); rockchip_set_read_margin(dev, info, target_rm, is_set_rm); restore_voltage: if (old_volt <= new_volt) regulator_set_voltage(vdd_reg, old_volt, INT_MAX); if (info->regulator_count > 1) regulator_set_voltage(mem_reg, old_volt_mem, INT_MAX); if (old_volt > new_volt) regulator_set_voltage(vdd_reg, old_volt, INT_MAX); disable_clk: clk_bulk_disable_unprepare(info->nclocks, info->clocks); return ret; } EXPORT_SYMBOL(rockchip_opp_check_rate_volt); int rockchip_init_opp_table(struct device *dev, struct rockchip_opp_info *info, char *clk_name, char *reg_name) { int ret = 0; ret = rockchip_init_opp_info(dev, info, clk_name, reg_name); if (ret) { dev_err(dev, "failed to init opp info\n"); return ret; } ret = dev_pm_opp_of_add_table(dev); if (ret) { dev_err(dev, "failed to add opp table\n"); rockchip_uninit_opp_info(dev, info); return ret; } rockchip_adjust_opp_table(dev, info); return 0; } EXPORT_SYMBOL(rockchip_init_opp_table); void rockchip_uninit_opp_table(struct device *dev, struct rockchip_opp_info *info) { dev_pm_opp_of_remove_table(dev); rockchip_uninit_opp_info(dev, info); } EXPORT_SYMBOL(rockchip_uninit_opp_table); MODULE_DESCRIPTION("ROCKCHIP OPP Select"); MODULE_AUTHOR("Finley Xiao , Liang Chen "); MODULE_LICENSE("GPL");