2602 lines
		
	
	
		
			64 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2602 lines
		
	
	
		
			64 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd
 | |
|  *
 | |
|  * SPDX-License-Identifier: GPL-2.0+
 | |
|  */
 | |
| //#define DEBUG
 | |
| #include <linux/clk.h>
 | |
| #include <linux/cpufreq.h>
 | |
| #include <linux/devfreq.h>
 | |
| #include <linux/mfd/syscon.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/nvmem-consumer.h>
 | |
| #include <linux/regmap.h>
 | |
| #include <linux/regulator/consumer.h>
 | |
| #include <linux/rockchip/rockchip_sip.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/soc/rockchip/pvtm.h>
 | |
| #include <linux/thermal.h>
 | |
| #include <linux/pm_opp.h>
 | |
| #include <linux/version.h>
 | |
| #include <soc/rockchip/rockchip_opp_select.h>
 | |
| 
 | |
| #include "../../clk/rockchip/clk.h"
 | |
| #include "../../opp/opp.h"
 | |
| #include "../../devfreq/governor.h"
 | |
| 
 | |
| #define MAX_PROP_NAME_LEN	6
 | |
| #define SEL_TABLE_END		~1
 | |
| #define AVS_DELETE_OPP		0
 | |
| #define AVS_SCALING_RATE	1
 | |
| 
 | |
| #define LEAKAGE_V1		1
 | |
| #define LEAKAGE_V2		2
 | |
| #define LEAKAGE_V3		3
 | |
| 
 | |
| #define to_thermal_opp_info(nb) container_of(nb, struct thermal_opp_info, \
 | |
| 					     thermal_nb)
 | |
| 
 | |
| struct sel_table {
 | |
| 	int min;
 | |
| 	int max;
 | |
| 	int sel;
 | |
| };
 | |
| 
 | |
| struct bin_sel_table {
 | |
| 	int bin;
 | |
| 	int sel;
 | |
| };
 | |
| 
 | |
| struct pvtm_config {
 | |
| 	unsigned int freq;
 | |
| 	unsigned int volt;
 | |
| 	unsigned int ch[2];
 | |
| 	unsigned int sample_time;
 | |
| 	unsigned int num;
 | |
| 	unsigned int err;
 | |
| 	unsigned int ref_temp;
 | |
| 	unsigned int offset;
 | |
| 	int temp_prop[2];
 | |
| 	const char *tz_name;
 | |
| 	struct thermal_zone_device *tz;
 | |
| };
 | |
| 
 | |
| struct 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 <finley.xiao@rock-chips.com>, Liang Chen <cl@rock-chips.com>");
 | |
| MODULE_LICENSE("GPL");
 |