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