diff options
Diffstat (limited to 'drivers/cpufreq/imx6q-cpufreq.c')
| -rw-r--r-- | drivers/cpufreq/imx6q-cpufreq.c | 305 | 
1 files changed, 171 insertions, 134 deletions
diff --git a/drivers/cpufreq/imx6q-cpufreq.c b/drivers/cpufreq/imx6q-cpufreq.c index 3e396543aea..af366c21d4b 100644 --- a/drivers/cpufreq/imx6q-cpufreq.c +++ b/drivers/cpufreq/imx6q-cpufreq.c @@ -7,12 +7,12 @@   */  #include <linux/clk.h> +#include <linux/cpu.h>  #include <linux/cpufreq.h> -#include <linux/delay.h>  #include <linux/err.h>  #include <linux/module.h>  #include <linux/of.h> -#include <linux/opp.h> +#include <linux/pm_opp.h>  #include <linux/platform_device.h>  #include <linux/regulator/consumer.h> @@ -34,77 +34,53 @@ static struct device *cpu_dev;  static struct cpufreq_frequency_table *freq_table;  static unsigned int transition_latency; -static int imx6q_verify_speed(struct cpufreq_policy *policy) -{ -	return cpufreq_frequency_table_verify(policy, freq_table); -} - -static unsigned int imx6q_get_speed(unsigned int cpu) -{ -	return clk_get_rate(arm_clk) / 1000; -} +static u32 *imx6_soc_volt; +static u32 soc_opp_count; -static int imx6q_set_target(struct cpufreq_policy *policy, -			    unsigned int target_freq, unsigned int relation) +static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)  { -	struct cpufreq_freqs freqs; -	struct opp *opp; +	struct dev_pm_opp *opp;  	unsigned long freq_hz, volt, volt_old; -	unsigned int index; +	unsigned int old_freq, new_freq;  	int ret; -	ret = cpufreq_frequency_table_target(policy, freq_table, target_freq, -					     relation, &index); -	if (ret) { -		dev_err(cpu_dev, "failed to match target frequency %d: %d\n", -			target_freq, ret); -		return ret; -	} - -	freqs.new = freq_table[index].frequency; -	freq_hz = freqs.new * 1000; -	freqs.old = clk_get_rate(arm_clk) / 1000; - -	if (freqs.old == freqs.new) -		return 0; +	new_freq = freq_table[index].frequency; +	freq_hz = new_freq * 1000; +	old_freq = clk_get_rate(arm_clk) / 1000;  	rcu_read_lock(); -	opp = opp_find_freq_ceil(cpu_dev, &freq_hz); +	opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);  	if (IS_ERR(opp)) {  		rcu_read_unlock();  		dev_err(cpu_dev, "failed to find OPP for %ld\n", freq_hz);  		return PTR_ERR(opp);  	} -	volt = opp_get_voltage(opp); +	volt = dev_pm_opp_get_voltage(opp);  	rcu_read_unlock();  	volt_old = regulator_get_voltage(arm_reg);  	dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV\n", -		freqs.old / 1000, volt_old / 1000, -		freqs.new / 1000, volt / 1000); - -	cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); +		old_freq / 1000, volt_old / 1000, +		new_freq / 1000, volt / 1000);  	/* scaling up?  scale voltage before frequency */ -	if (freqs.new > freqs.old) { +	if (new_freq > old_freq) { +		ret = regulator_set_voltage_tol(pu_reg, imx6_soc_volt[index], 0); +		if (ret) { +			dev_err(cpu_dev, "failed to scale vddpu up: %d\n", ret); +			return ret; +		} +		ret = regulator_set_voltage_tol(soc_reg, imx6_soc_volt[index], 0); +		if (ret) { +			dev_err(cpu_dev, "failed to scale vddsoc up: %d\n", ret); +			return ret; +		}  		ret = regulator_set_voltage_tol(arm_reg, volt, 0);  		if (ret) {  			dev_err(cpu_dev,  				"failed to scale vddarm up: %d\n", ret); -			freqs.new = freqs.old; -			goto post_notify; -		} - -		/* -		 * Need to increase vddpu and vddsoc for safety -		 * if we are about to run at 1.2 GHz. -		 */ -		if (freqs.new == FREQ_1P2_GHZ / 1000) { -			regulator_set_voltage_tol(pu_reg, -					PU_SOC_VOLTAGE_HIGH, 0); -			regulator_set_voltage_tol(soc_reg, -					PU_SOC_VOLTAGE_HIGH, 0); +			return ret;  		}  	} @@ -120,89 +96,72 @@ static int imx6q_set_target(struct cpufreq_policy *policy,  	clk_set_parent(step_clk, pll2_pfd2_396m_clk);  	clk_set_parent(pll1_sw_clk, step_clk);  	if (freq_hz > clk_get_rate(pll2_pfd2_396m_clk)) { -		clk_set_rate(pll1_sys_clk, freqs.new * 1000); +		clk_set_rate(pll1_sys_clk, new_freq * 1000);  		clk_set_parent(pll1_sw_clk, pll1_sys_clk);  	}  	/* Ensure the arm clock divider is what we expect */ -	ret = clk_set_rate(arm_clk, freqs.new * 1000); +	ret = clk_set_rate(arm_clk, new_freq * 1000);  	if (ret) {  		dev_err(cpu_dev, "failed to set clock rate: %d\n", ret);  		regulator_set_voltage_tol(arm_reg, volt_old, 0); -		freqs.new = freqs.old; -		goto post_notify; +		return ret;  	}  	/* scaling down?  scale voltage after frequency */ -	if (freqs.new < freqs.old) { +	if (new_freq < old_freq) {  		ret = regulator_set_voltage_tol(arm_reg, volt, 0);  		if (ret) {  			dev_warn(cpu_dev,  				 "failed to scale vddarm down: %d\n", ret);  			ret = 0;  		} - -		if (freqs.old == FREQ_1P2_GHZ / 1000) { -			regulator_set_voltage_tol(pu_reg, -					PU_SOC_VOLTAGE_NORMAL, 0); -			regulator_set_voltage_tol(soc_reg, -					PU_SOC_VOLTAGE_NORMAL, 0); +		ret = regulator_set_voltage_tol(soc_reg, imx6_soc_volt[index], 0); +		if (ret) { +			dev_warn(cpu_dev, "failed to scale vddsoc down: %d\n", ret); +			ret = 0; +		} +		ret = regulator_set_voltage_tol(pu_reg, imx6_soc_volt[index], 0); +		if (ret) { +			dev_warn(cpu_dev, "failed to scale vddpu down: %d\n", ret); +			ret = 0;  		}  	} -post_notify: -	cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); - -	return ret; -} - -static int imx6q_cpufreq_init(struct cpufreq_policy *policy) -{ -	int ret; - -	ret = cpufreq_frequency_table_cpuinfo(policy, freq_table); -	if (ret) { -		dev_err(cpu_dev, "invalid frequency table: %d\n", ret); -		return ret; -	} - -	policy->cpuinfo.transition_latency = transition_latency; -	policy->cur = clk_get_rate(arm_clk) / 1000; -	cpumask_setall(policy->cpus); -	cpufreq_frequency_table_get_attr(freq_table, policy->cpu); -  	return 0;  } -static int imx6q_cpufreq_exit(struct cpufreq_policy *policy) +static int imx6q_cpufreq_init(struct cpufreq_policy *policy)  { -	cpufreq_frequency_table_put_attr(policy->cpu); -	return 0; +	policy->clk = arm_clk; +	return cpufreq_generic_init(policy, freq_table, transition_latency);  } -static struct freq_attr *imx6q_cpufreq_attr[] = { -	&cpufreq_freq_attr_scaling_available_freqs, -	NULL, -}; -  static struct cpufreq_driver imx6q_cpufreq_driver = { -	.verify = imx6q_verify_speed, -	.target = imx6q_set_target, -	.get = imx6q_get_speed, +	.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK, +	.verify = cpufreq_generic_frequency_table_verify, +	.target_index = imx6q_set_target, +	.get = cpufreq_generic_get,  	.init = imx6q_cpufreq_init, -	.exit = imx6q_cpufreq_exit,  	.name = "imx6q-cpufreq", -	.attr = imx6q_cpufreq_attr, +	.attr = cpufreq_generic_attr,  };  static int imx6q_cpufreq_probe(struct platform_device *pdev)  {  	struct device_node *np; -	struct opp *opp; +	struct dev_pm_opp *opp;  	unsigned long min_volt, max_volt;  	int num, ret; - -	cpu_dev = &pdev->dev; +	const struct property *prop; +	const __be32 *val; +	u32 nr, i, j; + +	cpu_dev = get_cpu_device(0); +	if (!cpu_dev) { +		pr_err("failed to get cpu0 device\n"); +		return -ENODEV; +	}  	np = of_node_get(cpu_dev->of_node);  	if (!np) { @@ -210,73 +169,126 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev)  		return -ENOENT;  	} -	arm_clk = devm_clk_get(cpu_dev, "arm"); -	pll1_sys_clk = devm_clk_get(cpu_dev, "pll1_sys"); -	pll1_sw_clk = devm_clk_get(cpu_dev, "pll1_sw"); -	step_clk = devm_clk_get(cpu_dev, "step"); -	pll2_pfd2_396m_clk = devm_clk_get(cpu_dev, "pll2_pfd2_396m"); +	arm_clk = clk_get(cpu_dev, "arm"); +	pll1_sys_clk = clk_get(cpu_dev, "pll1_sys"); +	pll1_sw_clk = clk_get(cpu_dev, "pll1_sw"); +	step_clk = clk_get(cpu_dev, "step"); +	pll2_pfd2_396m_clk = clk_get(cpu_dev, "pll2_pfd2_396m");  	if (IS_ERR(arm_clk) || IS_ERR(pll1_sys_clk) || IS_ERR(pll1_sw_clk) ||  	    IS_ERR(step_clk) || IS_ERR(pll2_pfd2_396m_clk)) {  		dev_err(cpu_dev, "failed to get clocks\n");  		ret = -ENOENT; -		goto put_node; +		goto put_clk;  	} -	arm_reg = devm_regulator_get(cpu_dev, "arm"); -	pu_reg = devm_regulator_get(cpu_dev, "pu"); -	soc_reg = devm_regulator_get(cpu_dev, "soc"); +	arm_reg = regulator_get(cpu_dev, "arm"); +	pu_reg = regulator_get(cpu_dev, "pu"); +	soc_reg = regulator_get(cpu_dev, "soc");  	if (IS_ERR(arm_reg) || IS_ERR(pu_reg) || IS_ERR(soc_reg)) {  		dev_err(cpu_dev, "failed to get regulators\n");  		ret = -ENOENT; -		goto put_node; +		goto put_reg;  	} -	/* We expect an OPP table supplied by platform */ -	num = opp_get_opp_count(cpu_dev); +	/* +	 * We expect an OPP table supplied by platform. +	 * Just, incase the platform did not supply the OPP +	 * table, it will try to get it. +	 */ +	num = dev_pm_opp_get_opp_count(cpu_dev);  	if (num < 0) { -		ret = num; -		dev_err(cpu_dev, "no OPP table is found: %d\n", ret); -		goto put_node; +		ret = of_init_opp_table(cpu_dev); +		if (ret < 0) { +			dev_err(cpu_dev, "failed to init OPP table: %d\n", ret); +			goto put_reg; +		} + +		num = dev_pm_opp_get_opp_count(cpu_dev); +		if (num < 0) { +			ret = num; +			dev_err(cpu_dev, "no OPP table is found: %d\n", ret); +			goto put_reg; +		}  	} -	ret = opp_init_cpufreq_table(cpu_dev, &freq_table); +	ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);  	if (ret) {  		dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); -		goto put_node; +		goto put_reg; +	} + +	/* Make imx6_soc_volt array's size same as arm opp number */ +	imx6_soc_volt = devm_kzalloc(cpu_dev, sizeof(*imx6_soc_volt) * num, GFP_KERNEL); +	if (imx6_soc_volt == NULL) { +		ret = -ENOMEM; +		goto free_freq_table; +	} + +	prop = of_find_property(np, "fsl,soc-operating-points", NULL); +	if (!prop || !prop->value) +		goto soc_opp_out; + +	/* +	 * Each OPP is a set of tuples consisting of frequency and +	 * voltage like <freq-kHz vol-uV>. +	 */ +	nr = prop->length / sizeof(u32); +	if (nr % 2 || (nr / 2) < num) +		goto soc_opp_out; + +	for (j = 0; j < num; j++) { +		val = prop->value; +		for (i = 0; i < nr / 2; i++) { +			unsigned long freq = be32_to_cpup(val++); +			unsigned long volt = be32_to_cpup(val++); +			if (freq_table[j].frequency == freq) { +				imx6_soc_volt[soc_opp_count++] = volt; +				break; +			} +		} +	} + +soc_opp_out: +	/* use fixed soc opp volt if no valid soc opp info found in dtb */ +	if (soc_opp_count != num) { +		dev_warn(cpu_dev, "can NOT find valid fsl,soc-operating-points property in dtb, use default value!\n"); +		for (j = 0; j < num; j++) +			imx6_soc_volt[j] = PU_SOC_VOLTAGE_NORMAL; +		if (freq_table[num - 1].frequency * 1000 == FREQ_1P2_GHZ) +			imx6_soc_volt[num - 1] = PU_SOC_VOLTAGE_HIGH;  	}  	if (of_property_read_u32(np, "clock-latency", &transition_latency))  		transition_latency = CPUFREQ_ETERNAL;  	/* +	 * Calculate the ramp time for max voltage change in the +	 * VDDSOC and VDDPU regulators. +	 */ +	ret = regulator_set_voltage_time(soc_reg, imx6_soc_volt[0], imx6_soc_volt[num - 1]); +	if (ret > 0) +		transition_latency += ret * 1000; +	ret = regulator_set_voltage_time(pu_reg, imx6_soc_volt[0], imx6_soc_volt[num - 1]); +	if (ret > 0) +		transition_latency += ret * 1000; + +	/*  	 * OPP is maintained in order of increasing frequency, and  	 * freq_table initialised from OPP is therefore sorted in the  	 * same order.  	 */  	rcu_read_lock(); -	opp = opp_find_freq_exact(cpu_dev, +	opp = dev_pm_opp_find_freq_exact(cpu_dev,  				  freq_table[0].frequency * 1000, true); -	min_volt = opp_get_voltage(opp); -	opp = opp_find_freq_exact(cpu_dev, +	min_volt = dev_pm_opp_get_voltage(opp); +	opp = dev_pm_opp_find_freq_exact(cpu_dev,  				  freq_table[--num].frequency * 1000, true); -	max_volt = opp_get_voltage(opp); +	max_volt = dev_pm_opp_get_voltage(opp);  	rcu_read_unlock();  	ret = regulator_set_voltage_time(arm_reg, min_volt, max_volt);  	if (ret > 0)  		transition_latency += ret * 1000; -	/* Count vddpu and vddsoc latency in for 1.2 GHz support */ -	if (freq_table[num].frequency == FREQ_1P2_GHZ / 1000) { -		ret = regulator_set_voltage_time(pu_reg, PU_SOC_VOLTAGE_NORMAL, -						 PU_SOC_VOLTAGE_HIGH); -		if (ret > 0) -			transition_latency += ret * 1000; -		ret = regulator_set_voltage_time(soc_reg, PU_SOC_VOLTAGE_NORMAL, -						 PU_SOC_VOLTAGE_HIGH); -		if (ret > 0) -			transition_latency += ret * 1000; -	} -  	ret = cpufreq_register_driver(&imx6q_cpufreq_driver);  	if (ret) {  		dev_err(cpu_dev, "failed register driver: %d\n", ret); @@ -287,8 +299,25 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev)  	return 0;  free_freq_table: -	opp_free_cpufreq_table(cpu_dev, &freq_table); -put_node: +	dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); +put_reg: +	if (!IS_ERR(arm_reg)) +		regulator_put(arm_reg); +	if (!IS_ERR(pu_reg)) +		regulator_put(pu_reg); +	if (!IS_ERR(soc_reg)) +		regulator_put(soc_reg); +put_clk: +	if (!IS_ERR(arm_clk)) +		clk_put(arm_clk); +	if (!IS_ERR(pll1_sys_clk)) +		clk_put(pll1_sys_clk); +	if (!IS_ERR(pll1_sw_clk)) +		clk_put(pll1_sw_clk); +	if (!IS_ERR(step_clk)) +		clk_put(step_clk); +	if (!IS_ERR(pll2_pfd2_396m_clk)) +		clk_put(pll2_pfd2_396m_clk);  	of_node_put(np);  	return ret;  } @@ -296,7 +325,15 @@ put_node:  static int imx6q_cpufreq_remove(struct platform_device *pdev)  {  	cpufreq_unregister_driver(&imx6q_cpufreq_driver); -	opp_free_cpufreq_table(cpu_dev, &freq_table); +	dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); +	regulator_put(arm_reg); +	regulator_put(pu_reg); +	regulator_put(soc_reg); +	clk_put(arm_clk); +	clk_put(pll1_sys_clk); +	clk_put(pll1_sw_clk); +	clk_put(step_clk); +	clk_put(pll2_pfd2_396m_clk);  	return 0;  }  | 
