From f70575805708cabdedea7498aaa3f710fde4d920 Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Wed, 31 Jan 2024 03:29:01 +0000 Subject: [PATCH] add lvds1024*800 --- kernel/drivers/soc/rockchip/rockchip_opp_select.c | 1041 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 996 insertions(+), 45 deletions(-) diff --git a/kernel/drivers/soc/rockchip/rockchip_opp_select.c b/kernel/drivers/soc/rockchip/rockchip_opp_select.c index 1520e2f..6c496ad 100644 --- a/kernel/drivers/soc/rockchip/rockchip_opp_select.c +++ b/kernel/drivers/soc/rockchip/rockchip_opp_select.c @@ -3,12 +3,16 @@ * * SPDX-License-Identifier: GPL-2.0+ */ +//#define DEBUG #include <linux/clk.h> #include <linux/cpufreq.h> #include <linux/devfreq.h> +#include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/nvmem-consumer.h> +#include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <linux/rockchip/rockchip_sip.h> #include <linux/slab.h> #include <linux/soc/rockchip/pvtm.h> #include <linux/thermal.h> @@ -51,15 +55,24 @@ unsigned int num; unsigned int err; unsigned int ref_temp; + unsigned int offset; int temp_prop[2]; const char *tz_name; struct thermal_zone_device *tz; + struct regmap *grf; }; struct lkg_conversion_table { int temp; int conv; }; + +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 @@ -298,16 +311,8 @@ return -EINVAL; if (of_property_read_u32(np, "rockchip,pvtm-volt", &pvtm->volt)) return -EINVAL; - 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-sample-time", &pvtm->sample_time)) - 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; if (of_property_read_u32(np, "rockchip,pvtm-ref-temp", &pvtm->ref_temp)) return -EINVAL; @@ -324,6 +329,23 @@ if (IS_ERR(pvtm->tz)) return -EINVAL; if (!pvtm->tz->ops->get_temp) + return -EINVAL; + if (of_property_read_bool(np, "rockchip,pvtm-pvtpll")) { + if (of_property_read_u32(np, "rockchip,pvtm-offset", + &pvtm->offset)) + return -EINVAL; + pvtm->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(pvtm->grf)) + return -EINVAL; + return 0; + } + if (of_property_read_u32_array(np, "rockchip,pvtm-ch", pvtm->ch, 2)) + return -EINVAL; + if (pvtm->ch[0] >= PVTM_CH_MAX || pvtm->ch[1] >= PVTM_SUB_CH_MAX) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-number", &pvtm->num)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-error", &pvtm->err)) return -EINVAL; return 0; @@ -415,7 +437,7 @@ cur_temp, *target_value, avg_value, diff_value); resetore_volt: - regulator_set_voltage(reg, old_volt, old_volt); + regulator_set_voltage(reg, old_volt, INT_MAX); restore_clk: clk_set_rate(clk, old_freq); pvtm_value_out: @@ -694,6 +716,496 @@ } EXPORT_SYMBOL(rockchip_of_get_lkg_sel); +static unsigned long rockchip_pvtpll_get_rate(struct rockchip_opp_info *info) +{ + unsigned int rate0, rate1, delta; + int i; + +#define MIN_STABLE_DELTA 3 + regmap_read(info->grf, info->pvtpll_avg_offset, &rate0); + /* max delay 2ms */ + for (i = 0; i < 20; i++) { + udelay(100); + regmap_read(info->grf, info->pvtpll_avg_offset, &rate1); + delta = abs(rate1 - rate0); + rate0 = rate1; + if (delta <= MIN_STABLE_DELTA) + break; + } + + if (delta > MIN_STABLE_DELTA) { + dev_err(info->dev, "%s: bad delta: %u\n", __func__, delta); + return 0; + } + + return rate0 * 1000000; +} + +static int rockchip_pvtpll_parse_dt(struct rockchip_opp_info *info) +{ + struct device_node *np; + int ret; + + np = of_parse_phandle(info->dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(info->dev, "OPP-v2 not supported\n"); + return -ENOENT; + } + + ret = of_property_read_u32(np, "rockchip,pvtpll-avg-offset", &info->pvtpll_avg_offset); + if (ret) + goto out; + + ret = of_property_read_u32(np, "rockchip,pvtpll-min-rate", &info->pvtpll_min_rate); + if (ret) + goto out; + + ret = of_property_read_u32(np, "rockchip,pvtpll-volt-step", &info->pvtpll_volt_step); +out: + of_node_put(np); + + return ret; +} + +static int rockchip_init_pvtpll_info(struct rockchip_opp_info *info) +{ + struct opp_table *opp_table; + struct dev_pm_opp *opp; + int i = 0, max_count, ret; + + ret = rockchip_pvtpll_parse_dt(info); + if (ret) + return ret; + + max_count = dev_pm_opp_get_opp_count(info->dev); + if (max_count <= 0) + return max_count ? max_count : -ENODATA; + + info->opp_table = kcalloc(max_count, sizeof(*info->opp_table), GFP_KERNEL); + if (!info->opp_table) + return -ENOMEM; + + opp_table = dev_pm_opp_get_opp_table(info->dev); + if (!opp_table) { + kfree(info->opp_table); + info->opp_table = NULL; + return -ENOMEM; + } + + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + + info->opp_table[i].u_volt = opp->supplies[0].u_volt; + info->opp_table[i].u_volt_min = opp->supplies[0].u_volt_min; + info->opp_table[i].u_volt_max = opp->supplies[0].u_volt_max; + if (opp_table->regulator_count > 1) { + info->opp_table[i].u_volt_mem = opp->supplies[1].u_volt; + info->opp_table[i].u_volt_mem_min = opp->supplies[1].u_volt_min; + info->opp_table[i].u_volt_mem_max = opp->supplies[1].u_volt_max; + } + info->opp_table[i++].rate = opp->rate; + } + mutex_unlock(&opp_table->lock); + + dev_pm_opp_put_opp_table(opp_table); + + return 0; +} + +static int rockchip_pvtpll_set_volt(struct device *dev, struct regulator *reg, + int target_uV, int max_uV, char *reg_name) +{ + int ret = 0; + + ret = regulator_set_voltage(reg, target_uV, max_uV); + if (ret) + dev_err(dev, "%s: failed to set %s voltage (%d %d uV): %d\n", + __func__, reg_name, target_uV, max_uV, ret); + + return ret; +} + +static int rockchip_pvtpll_set_clk(struct device *dev, struct clk *clk, + unsigned long rate) +{ + int ret = 0; + + ret = clk_set_rate(clk, rate); + if (ret) + dev_err(dev, "%s: failed to set rate %lu Hz, ret:%d\n", + __func__, rate, ret); + + return ret; +} + +void rockchip_pvtpll_calibrate_opp(struct rockchip_opp_info *info) +{ + struct opp_table *opp_table; + struct dev_pm_opp *opp; + struct regulator *reg = NULL, *reg_mem = NULL; + unsigned long old_volt = 0, old_volt_mem = 0; + unsigned long volt = 0, volt_mem = 0; + unsigned long volt_min, volt_max, volt_mem_min, volt_mem_max; + unsigned long rate, pvtpll_rate, old_rate, cur_rate, delta0, delta1; + int i = 0, max_count, step, cur_step, ret; + + if (!info || !info->grf) + return; + + dev_dbg(info->dev, "calibrating opp ...\n"); + ret = rockchip_init_pvtpll_info(info); + if (ret) + return; + + max_count = dev_pm_opp_get_opp_count(info->dev); + if (max_count <= 0) + return; + + opp_table = dev_pm_opp_get_opp_table(info->dev); + if (!opp_table) + return; + + if ((!opp_table->regulators) || IS_ERR(opp_table->clk)) + goto out_put; + + reg = opp_table->regulators[0]; + old_volt = regulator_get_voltage(reg); + if (opp_table->regulator_count > 1) { + reg_mem = opp_table->regulators[1]; + old_volt_mem = regulator_get_voltage(reg_mem); + if (IS_ERR_VALUE(old_volt_mem)) + goto out_put; + } + old_rate = clk_get_rate(opp_table->clk); + if (IS_ERR_VALUE(old_volt) || IS_ERR_VALUE(old_rate)) + goto out_put; + cur_rate = old_rate; + + step = regulator_get_linear_step(reg); + if (!step || info->pvtpll_volt_step > step) + step = info->pvtpll_volt_step; + + if (old_rate > info->pvtpll_min_rate * 1000) { + if (rockchip_pvtpll_set_clk(info->dev, opp_table->clk, + info->pvtpll_min_rate * 1000)) + goto out_put; + } + + for (i = 0; i < max_count; i++) { + rate = info->opp_table[i].rate; + if (rate < 1000 * info->pvtpll_min_rate) + continue; + + volt = max(volt, info->opp_table[i].u_volt); + volt_min = info->opp_table[i].u_volt_min; + volt_max = info->opp_table[i].u_volt_max; + + if (opp_table->regulator_count > 1) { + volt_mem = max(volt_mem, info->opp_table[i].u_volt_mem); + volt_mem_min = info->opp_table[i].u_volt_mem_min; + volt_mem_max = info->opp_table[i].u_volt_mem_max; + if (rockchip_pvtpll_set_volt(info->dev, reg_mem, + volt_mem, volt_mem_max, "mem")) + goto out; + } + if (rockchip_pvtpll_set_volt(info->dev, reg, volt, volt_max, "vdd")) + goto out; + + if (rockchip_pvtpll_set_clk(info->dev, opp_table->clk, rate)) + goto out; + cur_rate = rate; + pvtpll_rate = rockchip_pvtpll_get_rate(info); + if (!pvtpll_rate) + goto out; + cur_step = (pvtpll_rate < rate) ? step : -step; + delta1 = abs(pvtpll_rate - rate); + do { + delta0 = delta1; + volt += cur_step; + if ((volt < volt_min) || (volt > volt_max)) + break; + if (opp_table->regulator_count > 1) { + if (volt > volt_mem_max) + break; + else if (volt < volt_mem_min) + volt_mem = volt_mem_min; + else + volt_mem = volt; + if (rockchip_pvtpll_set_volt(info->dev, reg_mem, + volt_mem, volt_mem_max, + "mem")) + break; + } + if (rockchip_pvtpll_set_volt(info->dev, reg, volt, + volt_max, "vdd")) + break; + pvtpll_rate = rockchip_pvtpll_get_rate(info); + if (!pvtpll_rate) + goto out; + delta1 = abs(pvtpll_rate - rate); + } while (delta1 < delta0); + + volt -= cur_step; + info->opp_table[i].u_volt = volt; + if (opp_table->regulator_count > 1) { + if (volt < volt_mem_min) + volt_mem = volt_mem_min; + else + volt_mem = volt; + info->opp_table[i].u_volt_mem = volt_mem; + } + } + + i = 0; + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + + opp->supplies[0].u_volt = info->opp_table[i].u_volt; + if (opp_table->regulator_count > 1) + opp->supplies[1].u_volt = info->opp_table[i].u_volt_mem; + i++; + } + mutex_unlock(&opp_table->lock); + dev_info(info->dev, "opp calibration done\n"); +out: + if (cur_rate > old_rate) + rockchip_pvtpll_set_clk(info->dev, opp_table->clk, old_rate); + if (opp_table->regulator_count > 1) + rockchip_pvtpll_set_volt(info->dev, reg_mem, old_volt_mem, + INT_MAX, "mem"); + rockchip_pvtpll_set_volt(info->dev, reg, old_volt, INT_MAX, "vdd"); + if (cur_rate < old_rate) + rockchip_pvtpll_set_clk(info->dev, opp_table->clk, old_rate); +out_put: + dev_pm_opp_put_opp_table(opp_table); +} +EXPORT_SYMBOL(rockchip_pvtpll_calibrate_opp); + +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->rate < min_rate * 1000 || opp->rate > max_rate * 1000) + continue; + ret = clk_set_rate(opp_table->clk, opp->rate | opp_flag); + if (ret) { + dev_err(info->dev, + "failed to change %lu len margin %d\n", + opp->rate, 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); +} +EXPORT_SYMBOL(rockchip_pvtpll_add_length); + +void rockchip_init_pvtpll_table(struct rockchip_opp_info *info, int bin) +{ + 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(info->dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(info->dev, "OPP-v2 not supported\n"); + return; + } + + ret = of_parse_phandle_with_args(info->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 (bin > 0) { + snprintf(prop_name, sizeof(prop_name), + "rockchip,pvtpll-table-B%d", 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(info->dev, "%s: Invalid %s property (%d)\n", + __func__, prop_name, count); + goto out; + } else if (count % 5) { + dev_err(info->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(info->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(info->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); +} +EXPORT_SYMBOL(rockchip_init_pvtpll_table); + +static int rockchip_get_pvtm_pvtpll(struct device *dev, struct device_node *np, + char *reg_name) +{ + struct regulator *reg; + struct clk *clk; + struct pvtm_config *pvtm; + unsigned long old_freq; + unsigned int old_volt; + int cur_temp, diff_temp, prop_temp, diff_value; + int pvtm_value = 0; + int ret = 0; + + 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; + } + + 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(pvtm->grf, pvtm->offset, &pvtm_value); + if (ret < 0) { + dev_err(dev, "failed to get pvtm from 0x%x\n", pvtm->offset); + goto resetore_volt; + } + pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); + diff_temp = (cur_temp / 1000 - pvtm->ref_temp); + if (diff_temp < 0) + prop_temp = pvtm->temp_prop[0]; + else + prop_temp = pvtm->temp_prop[1]; + diff_value = diff_temp * prop_temp / 1000; + pvtm_value += diff_value; + + dev_info(dev, "pvtm=%d\n", pvtm_value); + +resetore_volt: + regulator_set_voltage(reg, old_volt, 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, char *reg_name) @@ -743,14 +1255,18 @@ } void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np, - char *reg_name, int process, + char *reg_name, int bin, int process, int *volt_sel, int *scale_sel) { struct property *prop = NULL; char name[NAME_MAX]; int pvtm, ret; + u32 hw = 0; - pvtm = rockchip_get_pvtm(dev, np, reg_name); + if (of_property_read_bool(np, "rockchip,pvtm-pvtpll")) + pvtm = rockchip_get_pvtm_pvtpll(dev, np, reg_name); + else + pvtm = rockchip_get_pvtm(dev, np, reg_name); if (pvtm <= 0) return; @@ -760,6 +1276,17 @@ snprintf(name, sizeof(name), "rockchip,p%d-pvtm-voltage-sel", process); prop = of_find_property(np, name, NULL); + } else if (bin > 0) { + of_property_read_u32(np, "rockchip,pvtm-hw", &hw); + if (hw && (hw & BIT(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", bin); + prop = of_find_property(np, name, NULL); + } } if (!prop) sprintf(name, "rockchip,pvtm-voltage-sel"); @@ -770,6 +1297,7 @@ next: if (!scale_sel) return; + prop = NULL; if (process >= 0) { snprintf(name, sizeof(name), "rockchip,p%d-pvtm-scaling-sel", process); @@ -813,37 +1341,99 @@ } EXPORT_SYMBOL(rockchip_of_get_bin_volt_sel); -void rockchip_get_soc_info(struct device *dev, - const struct of_device_id *matches, - int *bin, int *process) +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 *np; struct device_node *node; - int (*get_soc_info)(struct device *dev, struct device_node *np, - int *bin, int *process); - int ret = 0; - - if (!matches) - return; - - np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); - if (!np) { - dev_warn(dev, "OPP-v2 not supported\n"); - return; - } node = of_find_node_by_path("/"); match = of_match_node(matches, node); - if (match && match->data) { - get_soc_info = match->data; - ret = get_soc_info(dev, np, bin, process); - if (ret) - dev_err(dev, "Failed to get soc info\n"); + if (match && match->data) + info->data = match->data; + of_node_put(node); +} +EXPORT_SYMBOL(rockchip_get_opp_data); + +int rockchip_get_volt_rm_table(struct device *dev, struct device_node *np, + char *porp_name, struct volt_rm_table **table) +{ + struct volt_rm_table *rm_table; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + + if (count % 2) + return -EINVAL; + + rm_table = devm_kzalloc(dev, sizeof(*rm_table) * (count / 2 + 1), + GFP_KERNEL); + if (!rm_table) + return -ENOMEM; + + for (i = 0; i < count / 2; i++) { + of_property_read_u32_index(np, porp_name, 2 * i, + &rm_table[i].volt); + of_property_read_u32_index(np, porp_name, 2 * i + 1, + &rm_table[i].rm); } - of_node_put(node); - of_node_put(np); + rm_table[i].volt = 0; + rm_table[i].rm = VOLT_RM_TABLE_END; + + *table = rm_table; + + return 0; +} +EXPORT_SYMBOL(rockchip_get_volt_rm_table); + +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; } EXPORT_SYMBOL(rockchip_get_soc_info); @@ -864,7 +1454,7 @@ rockchip_of_get_lkg_sel(dev, np, lkg_name, process, &lkg_volt_sel, &lkg_scale); - rockchip_of_get_pvtm_sel(dev, np, reg_name, process, + rockchip_of_get_pvtm_sel(dev, np, reg_name, bin, process, &pvtm_volt_sel, &pvtm_scale); rockchip_of_get_bin_sel(dev, np, bin, &bin_scale); rockchip_of_get_bin_volt_sel(dev, np, bin, &bin_volt_sel); @@ -902,6 +1492,42 @@ } EXPORT_SYMBOL(rockchip_set_opp_prop_name); +struct opp_table *rockchip_set_opp_supported_hw(struct device *dev, + struct device_node *np, + int bin, int volt_sel) +{ + struct opp_table *opp_table; + u32 supported_hw[2]; + u32 version = 0, speed = 0; + + if (!of_property_read_bool(np, "rockchip,supported-hw")) + return NULL; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (!opp_table) + return NULL; + if (opp_table->supported_hw) { + dev_pm_opp_put_opp_table(opp_table); + return NULL; + } + dev_pm_opp_put_opp_table(opp_table); + + if (bin >= 0) + version = bin; + if (volt_sel >= 0) + speed = volt_sel; + + /* SoC Version */ + supported_hw[0] = BIT(version); + /* Speed Grade */ + supported_hw[1] = BIT(speed); + + dev_info(dev, "soc version=%d, speed=%d\n", version, speed); + + return dev_pm_opp_set_supported_hw(dev, supported_hw, 2); +} +EXPORT_SYMBOL(rockchip_set_opp_supported_hw); + static int rockchip_adjust_opp_by_irdrop(struct device *dev, struct device_node *np, unsigned long *safe_rate, @@ -910,8 +1536,9 @@ 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 tmp_safe_rate = 0, opp_rate, i, ret = 0; + int opp_rate, i, ret = 0; u32 max_volt = UINT_MAX; bool reach_max_volt = false; @@ -927,6 +1554,8 @@ 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 { @@ -995,12 +1624,57 @@ 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->rate < opp_info.min_freq * 1000000) + continue; + if (opp->rate > 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_table(struct device *dev, @@ -1024,7 +1698,7 @@ goto out; } if (opp->rate > scale_rate) - dev_pm_opp_remove(dev, opp->rate); + dev_pm_opp_disable(dev, opp->rate); dev_pm_opp_put(opp); } out: @@ -1049,10 +1723,15 @@ 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 && !scale) + goto out_np; + clk = of_clk_get_by_name(np, NULL); if (IS_ERR(clk)) { if (!safe_rate) @@ -1066,14 +1745,14 @@ if (safe_rate) irdrop_scale = rockchip_pll_clk_rate_to_scale(clk, safe_rate); - if (max_rate) - opp_scale = rockchip_pll_clk_rate_to_scale(clk, max_rate); target_scale = max(irdrop_scale, scale); if (target_scale <= 0) goto out_clk; dev_dbg(dev, "target_scale=%d, irdrop_scale=%d, scale=%d\n", target_scale, irdrop_scale, scale); + if (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) @@ -1118,14 +1797,219 @@ } EXPORT_SYMBOL(rockchip_adjust_power_scale); -int rockchip_init_opp_table(struct device *dev, - const struct of_device_id *matches, +int rockchip_get_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + unsigned long volt, u32 *target_rm) +{ + int i; + + if (!opp_info || !opp_info->volt_rm_tbl) + return 0; + + for (i = 0; opp_info->volt_rm_tbl[i].rm != VOLT_RM_TABLE_END; i++) { + if (volt >= opp_info->volt_rm_tbl[i].volt) { + opp_info->target_rm = opp_info->volt_rm_tbl[i].rm; + break; + } + } + *target_rm = opp_info->target_rm; + + return 0; +} +EXPORT_SYMBOL(rockchip_get_read_margin); + +int rockchip_set_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, u32 rm, + bool is_set_rm) +{ + if (!is_set_rm || !opp_info) + return 0; + if (!opp_info || !opp_info->volt_rm_tbl) + return 0; + if (!opp_info->data || !opp_info->data->set_read_margin) + return 0; + if (rm == opp_info->current_rm) + return 0; + + return opp_info->data->set_read_margin(dev, opp_info, rm); +} +EXPORT_SYMBOL(rockchip_set_read_margin); + +int rockchip_init_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + char *reg_name) +{ + struct clk *clk; + struct regulator *reg; + unsigned long cur_rate; + int cur_volt, ret = 0; + u32 target_rm = UINT_MAX; + + reg = regulator_get_optional(dev, reg_name); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + if (ret != -EPROBE_DEFER) + dev_err(dev, "%s: no regulator (%s) found: %d\n", + __func__, reg_name, ret); + return ret; + } + cur_volt = regulator_get_voltage(reg); + if (cur_volt < 0) { + ret = cur_volt; + if (ret != -EPROBE_DEFER) + dev_err(dev, "%s: failed to get (%s) volt: %d\n", + __func__, reg_name, ret); + goto out; + } + + clk = clk_get(dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(dev, "%s: failed to get clk: %d\n", __func__, ret); + goto out; + } + cur_rate = clk_get_rate(clk); + + rockchip_get_read_margin(dev, opp_info, cur_volt, &target_rm); + dev_dbg(dev, "cur_rate=%lu, threshold=%lu, cur_volt=%d, target_rm=%d\n", + cur_rate, opp_info->intermediate_threshold_freq, + cur_volt, target_rm); + if (opp_info->intermediate_threshold_freq && + cur_rate > opp_info->intermediate_threshold_freq) { + clk_set_rate(clk, opp_info->intermediate_threshold_freq); + rockchip_set_read_margin(dev, opp_info, target_rm, true); + clk_set_rate(clk, cur_rate); + } else { + rockchip_set_read_margin(dev, opp_info, target_rm, true); + } + + clk_put(clk); +out: + regulator_put(reg); + + return ret; +} +EXPORT_SYMBOL(rockchip_init_read_margin); + +int rockchip_set_intermediate_rate(struct device *dev, + struct rockchip_opp_info *opp_info, + struct clk *clk, unsigned long old_freq, + unsigned long new_freq, bool is_scaling_up, + bool is_set_clk) +{ + if (!is_set_clk) + return 0; + if (!opp_info || !opp_info->volt_rm_tbl) + return 0; + if (!opp_info->data || !opp_info->data->set_read_margin) + return 0; + if (opp_info->target_rm == opp_info->current_rm) + return 0; + /* + * There is no need to set intermediate rate if the new voltage + * and the current voltage are high voltage. + */ + if ((opp_info->target_rm < opp_info->low_rm) && + (opp_info->current_rm < opp_info->low_rm)) + return 0; + + if (is_scaling_up) { + /* + * If scaling up and the current frequency is less than + * or equal to intermediate threshold frequency, there is + * no need to set intermediate rate. + */ + if (opp_info->intermediate_threshold_freq && + old_freq <= opp_info->intermediate_threshold_freq) + return 0; + return clk_set_rate(clk, new_freq | OPP_SCALING_UP_INTER); + } + /* + * If scaling down and the new frequency is less than or equal to + * intermediate threshold frequency , there is no need to set + * intermediate rate and set the new frequency directly. + */ + if (opp_info->intermediate_threshold_freq && + new_freq <= opp_info->intermediate_threshold_freq) + return clk_set_rate(clk, new_freq); + + return clk_set_rate(clk, new_freq | OPP_SCALING_DOWN_INTER); +} +EXPORT_SYMBOL(rockchip_set_intermediate_rate); + +static int rockchip_get_opp_clk(struct device *dev, struct device_node *np, + struct rockchip_opp_info *info) +{ + struct clk_bulk_data *clks; + struct of_phandle_args clkspec; + int ret = 0, num_clks = 0, i; + + if (of_find_property(np, "rockchip,opp-clocks", NULL)) { + num_clks = of_count_phandle_with_args(np, "rockchip,opp-clocks", + "#clock-cells"); + if (num_clks <= 0) + return 0; + clks = devm_kcalloc(dev, num_clks, sizeof(*clks), GFP_KERNEL); + if (!clks) + return -ENOMEM; + for (i = 0; i < num_clks; 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; + } + clks[i].clk = of_clk_get_from_provider(&clkspec); + of_node_put(clkspec.np); + if (IS_ERR(clks[i].clk)) { + ret = PTR_ERR(clks[i].clk); + clks[i].clk = NULL; + dev_err(dev, "%s: failed to get opp clk %d\n", + np->name, i); + goto error; + } + } + } else { + num_clks = of_clk_get_parent_count(np); + if (num_clks <= 0) + return 0; + clks = devm_kcalloc(dev, num_clks, sizeof(*clks), GFP_KERNEL); + if (!clks) + return -ENOMEM; + for (i = 0; i < num_clks; i++) { + clks[i].clk = of_clk_get(np, i); + if (IS_ERR(clks[i].clk)) { + ret = PTR_ERR(clks[i].clk); + clks[i].clk = NULL; + dev_err(dev, "%s: failed to get clk %d\n", + np->name, i); + goto error; + } + } + } + info->clks = clks; + info->num_clks = num_clks; + + return 0; +error: + while (--i >= 0) + clk_put(clks[i].clk); + devm_kfree(dev, clks); + + return ret; +} + +int rockchip_init_opp_table(struct device *dev, struct rockchip_opp_info *info, char *lkg_name, char *reg_name) { struct device_node *np; int bin = -EINVAL, process = -EINVAL; int scale = 0, volt_sel = -EINVAL; int ret = 0; + u32 freq; /* Get OPP descriptor node */ np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); @@ -1133,23 +2017,90 @@ dev_dbg(dev, "Failed to find operating-points-v2\n"); return -ENOENT; } - of_node_put(np); + if (!info) + goto next; + info->dev = dev; - rockchip_get_soc_info(dev, matches, &bin, &process); + ret = rockchip_get_opp_clk(dev, np, info); + if (ret) + goto out; + if (info->clks) { + ret = clk_bulk_prepare_enable(info->num_clks, info->clks); + if (ret) { + dev_err(dev, "failed to enable opp clks\n"); + goto out; + } + } + if (info->data && info->data->set_read_margin) { + info->current_rm = UINT_MAX; + info->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(info->grf)) + info->grf = NULL; + rockchip_get_volt_rm_table(dev, np, "volt-mem-read-margin", + &info->volt_rm_tbl); + of_property_read_u32(np, "low-volt-mem-read-margin", + &info->low_rm); + if (!of_property_read_u32(np, "intermediate-threshold-freq", + &freq)) + info->intermediate_threshold_freq = freq * 1000; + rockchip_init_read_margin(dev, info, reg_name); + } + if (info->data && info->data->get_soc_info) + info->data->get_soc_info(dev, np, &bin, &process); + +next: + rockchip_get_soc_info(dev, np, &bin, &process); + rockchip_init_pvtpll_table(info, bin); rockchip_get_scale_volt_sel(dev, lkg_name, reg_name, bin, process, &scale, &volt_sel); + if (info && info->data && info->data->set_soc_info) + info->data->set_soc_info(dev, np, bin, process, volt_sel); rockchip_set_opp_prop_name(dev, process, volt_sel); + rockchip_set_opp_supported_hw(dev, np, bin, volt_sel); ret = dev_pm_opp_of_add_table(dev); if (ret) { dev_err(dev, "Invalid operating-points in device tree.\n"); - return ret; + goto dis_opp_clk; } rockchip_adjust_power_scale(dev, scale); + rockchip_pvtpll_calibrate_opp(info); + rockchip_pvtpll_add_length(info); - return 0; +dis_opp_clk: + if (info && info->clks) + clk_bulk_disable_unprepare(info->num_clks, info->clks); +out: + of_node_put(np); + + return ret; } EXPORT_SYMBOL(rockchip_init_opp_table); +void rockchip_uninit_opp_table(struct device *dev, struct rockchip_opp_info *info) +{ + struct opp_table *opp_table; + + if (info) { + kfree(info->opp_table); + info->opp_table = NULL; + devm_kfree(dev, info->clks); + info->clks = NULL; + devm_kfree(dev, info->volt_rm_tbl); + info->volt_rm_tbl = NULL; + } + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) + return; + dev_pm_opp_of_remove_table(dev); + if (opp_table->prop_name) + dev_pm_opp_put_prop_name(opp_table); + if (opp_table->supported_hw) + dev_pm_opp_put_supported_hw(opp_table); + dev_pm_opp_put_opp_table(opp_table); +} +EXPORT_SYMBOL(rockchip_uninit_opp_table); + MODULE_DESCRIPTION("ROCKCHIP OPP Select"); MODULE_AUTHOR("Finley Xiao <finley.xiao@rock-chips.com>, Liang Chen <cl@rock-chips.com>"); MODULE_LICENSE("GPL"); -- Gitblit v1.6.2