diff options
Diffstat (limited to 'drivers/cpufreq/cppc_cpufreq.c')
| -rw-r--r-- | drivers/cpufreq/cppc_cpufreq.c | 204 | 
1 files changed, 186 insertions, 18 deletions
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index a1c3025f9df7..30f302149730 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -20,6 +20,7 @@  #include <linux/cpu.h>  #include <linux/cpufreq.h>  #include <linux/dmi.h> +#include <linux/time.h>  #include <linux/vmalloc.h>  #include <asm/unaligned.h> @@ -41,9 +42,6 @@   */  static struct cppc_cpudata **all_cpu_data; -/* Capture the max KHz from DMI */ -static u64 cppc_dmi_max_khz; -  /* Callback function used to retrieve the max frequency from DMI */  static void cppc_find_dmi_mhz(const struct dmi_header *dm, void *private)  { @@ -74,6 +72,64 @@ static u64 cppc_get_dmi_max_khz(void)  	return (1000 * mhz);  } +/* + * If CPPC lowest_freq and nominal_freq registers are exposed then we can + * use them to convert perf to freq and vice versa + * + * If the perf/freq point lies between Nominal and Lowest, we can treat + * (Low perf, Low freq) and (Nom Perf, Nom freq) as 2D co-ordinates of a line + * and extrapolate the rest + * For perf/freq > Nominal, we use the ratio perf:freq at Nominal for conversion + */ +static unsigned int cppc_cpufreq_perf_to_khz(struct cppc_cpudata *cpu, +					unsigned int perf) +{ +	static u64 max_khz; +	struct cppc_perf_caps *caps = &cpu->perf_caps; +	u64 mul, div; + +	if (caps->lowest_freq && caps->nominal_freq) { +		if (perf >= caps->nominal_perf) { +			mul = caps->nominal_freq; +			div = caps->nominal_perf; +		} else { +			mul = caps->nominal_freq - caps->lowest_freq; +			div = caps->nominal_perf - caps->lowest_perf; +		} +	} else { +		if (!max_khz) +			max_khz = cppc_get_dmi_max_khz(); +		mul = max_khz; +		div = cpu->perf_caps.highest_perf; +	} +	return (u64)perf * mul / div; +} + +static unsigned int cppc_cpufreq_khz_to_perf(struct cppc_cpudata *cpu, +					unsigned int freq) +{ +	static u64 max_khz; +	struct cppc_perf_caps *caps = &cpu->perf_caps; +	u64  mul, div; + +	if (caps->lowest_freq && caps->nominal_freq) { +		if (freq >= caps->nominal_freq) { +			mul = caps->nominal_perf; +			div = caps->nominal_freq; +		} else { +			mul = caps->lowest_perf; +			div = caps->lowest_freq; +		} +	} else { +		if (!max_khz) +			max_khz = cppc_get_dmi_max_khz(); +		mul = cpu->perf_caps.highest_perf; +		div = max_khz; +	} + +	return (u64)freq * mul / div; +} +  static int cppc_cpufreq_set_target(struct cpufreq_policy *policy,  		unsigned int target_freq,  		unsigned int relation) @@ -85,7 +141,7 @@ static int cppc_cpufreq_set_target(struct cpufreq_policy *policy,  	cpu = all_cpu_data[policy->cpu]; -	desired_perf = (u64)target_freq * cpu->perf_caps.highest_perf / cppc_dmi_max_khz; +	desired_perf = cppc_cpufreq_khz_to_perf(cpu, target_freq);  	/* Return if it is exactly the same perf */  	if (desired_perf == cpu->perf_ctrls.desired_perf)  		return ret; @@ -125,6 +181,49 @@ static void cppc_cpufreq_stop_cpu(struct cpufreq_policy *policy)  				cpu->perf_caps.lowest_perf, cpu_num, ret);  } +/* + * The PCC subspace describes the rate at which platform can accept commands + * on the shared PCC channel (including READs which do not count towards freq + * trasition requests), so ideally we need to use the PCC values as a fallback + * if we don't have a platform specific transition_delay_us + */ +#ifdef CONFIG_ARM64 +#include <asm/cputype.h> + +static unsigned int cppc_cpufreq_get_transition_delay_us(int cpu) +{ +	unsigned long implementor = read_cpuid_implementor(); +	unsigned long part_num = read_cpuid_part_number(); +	unsigned int delay_us = 0; + +	switch (implementor) { +	case ARM_CPU_IMP_QCOM: +		switch (part_num) { +		case QCOM_CPU_PART_FALKOR_V1: +		case QCOM_CPU_PART_FALKOR: +			delay_us = 10000; +			break; +		default: +			delay_us = cppc_get_transition_latency(cpu) / NSEC_PER_USEC; +			break; +		} +		break; +	default: +		delay_us = cppc_get_transition_latency(cpu) / NSEC_PER_USEC; +		break; +	} + +	return delay_us; +} + +#else + +static unsigned int cppc_cpufreq_get_transition_delay_us(int cpu) +{ +	return cppc_get_transition_latency(cpu) / NSEC_PER_USEC; +} +#endif +  static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)  {  	struct cppc_cpudata *cpu; @@ -142,31 +241,41 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)  		return ret;  	} -	cppc_dmi_max_khz = cppc_get_dmi_max_khz(); +	/* Convert the lowest and nominal freq from MHz to KHz */ +	cpu->perf_caps.lowest_freq *= 1000; +	cpu->perf_caps.nominal_freq *= 1000;  	/*  	 * Set min to lowest nonlinear perf to avoid any efficiency penalty (see  	 * Section 8.4.7.1.1.5 of ACPI 6.1 spec)  	 */ -	policy->min = cpu->perf_caps.lowest_nonlinear_perf * cppc_dmi_max_khz / -		cpu->perf_caps.highest_perf; -	policy->max = cppc_dmi_max_khz; +	policy->min = cppc_cpufreq_perf_to_khz(cpu, cpu->perf_caps.lowest_nonlinear_perf); +	policy->max = cppc_cpufreq_perf_to_khz(cpu, cpu->perf_caps.highest_perf);  	/*  	 * Set cpuinfo.min_freq to Lowest to make the full range of performance  	 * available if userspace wants to use any perf between lowest & lowest  	 * nonlinear perf  	 */ -	policy->cpuinfo.min_freq = cpu->perf_caps.lowest_perf * cppc_dmi_max_khz / -		cpu->perf_caps.highest_perf; -	policy->cpuinfo.max_freq = cppc_dmi_max_khz; +	policy->cpuinfo.min_freq = cppc_cpufreq_perf_to_khz(cpu, cpu->perf_caps.lowest_perf); +	policy->cpuinfo.max_freq = cppc_cpufreq_perf_to_khz(cpu, cpu->perf_caps.highest_perf); -	policy->cpuinfo.transition_latency = cppc_get_transition_latency(cpu_num); +	policy->transition_delay_us = cppc_cpufreq_get_transition_delay_us(cpu_num);  	policy->shared_type = cpu->shared_type; -	if (policy->shared_type == CPUFREQ_SHARED_TYPE_ANY) +	if (policy->shared_type == CPUFREQ_SHARED_TYPE_ANY) { +		int i; +  		cpumask_copy(policy->cpus, cpu->shared_cpu_map); -	else if (policy->shared_type == CPUFREQ_SHARED_TYPE_ALL) { + +		for_each_cpu(i, policy->cpus) { +			if (unlikely(i == policy->cpu)) +				continue; + +			memcpy(&all_cpu_data[i]->perf_caps, &cpu->perf_caps, +			       sizeof(cpu->perf_caps)); +		} +	} else if (policy->shared_type == CPUFREQ_SHARED_TYPE_ALL) {  		/* Support only SW_ANY for now. */  		pr_debug("Unsupported CPU co-ord type\n");  		return -EFAULT; @@ -175,7 +284,8 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)  	cpu->cur_policy = policy;  	/* Set policy->cur to max now. The governors will adjust later. */ -	policy->cur = cppc_dmi_max_khz; +	policy->cur = cppc_cpufreq_perf_to_khz(cpu, +					cpu->perf_caps.highest_perf);  	cpu->perf_ctrls.desired_perf = cpu->perf_caps.highest_perf;  	ret = cppc_set_perf(cpu_num, &cpu->perf_ctrls); @@ -186,10 +296,62 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)  	return ret;  } +static inline u64 get_delta(u64 t1, u64 t0) +{ +	if (t1 > t0 || t0 > ~(u32)0) +		return t1 - t0; + +	return (u32)t1 - (u32)t0; +} + +static int cppc_get_rate_from_fbctrs(struct cppc_cpudata *cpu, +				     struct cppc_perf_fb_ctrs fb_ctrs_t0, +				     struct cppc_perf_fb_ctrs fb_ctrs_t1) +{ +	u64 delta_reference, delta_delivered; +	u64 reference_perf, delivered_perf; + +	reference_perf = fb_ctrs_t0.reference_perf; + +	delta_reference = get_delta(fb_ctrs_t1.reference, +				    fb_ctrs_t0.reference); +	delta_delivered = get_delta(fb_ctrs_t1.delivered, +				    fb_ctrs_t0.delivered); + +	/* Check to avoid divide-by zero */ +	if (delta_reference || delta_delivered) +		delivered_perf = (reference_perf * delta_delivered) / +					delta_reference; +	else +		delivered_perf = cpu->perf_ctrls.desired_perf; + +	return cppc_cpufreq_perf_to_khz(cpu, delivered_perf); +} + +static unsigned int cppc_cpufreq_get_rate(unsigned int cpunum) +{ +	struct cppc_perf_fb_ctrs fb_ctrs_t0 = {0}, fb_ctrs_t1 = {0}; +	struct cppc_cpudata *cpu = all_cpu_data[cpunum]; +	int ret; + +	ret = cppc_get_perf_ctrs(cpunum, &fb_ctrs_t0); +	if (ret) +		return ret; + +	udelay(2); /* 2usec delay between sampling */ + +	ret = cppc_get_perf_ctrs(cpunum, &fb_ctrs_t1); +	if (ret) +		return ret; + +	return cppc_get_rate_from_fbctrs(cpu, fb_ctrs_t0, fb_ctrs_t1); +} +  static struct cpufreq_driver cppc_cpufreq_driver = {  	.flags = CPUFREQ_CONST_LOOPS,  	.verify = cppc_verify_policy,  	.target = cppc_cpufreq_set_target, +	.get = cppc_cpufreq_get_rate,  	.init = cppc_cpufreq_cpu_init,  	.stop_cpu = cppc_cpufreq_stop_cpu,  	.name = "cppc_cpufreq", @@ -203,7 +365,8 @@ static int __init cppc_cpufreq_init(void)  	if (acpi_disabled)  		return -ENODEV; -	all_cpu_data = kzalloc(sizeof(void *) * num_possible_cpus(), GFP_KERNEL); +	all_cpu_data = kcalloc(num_possible_cpus(), sizeof(void *), +			       GFP_KERNEL);  	if (!all_cpu_data)  		return -ENOMEM; @@ -230,8 +393,13 @@ static int __init cppc_cpufreq_init(void)  	return ret;  out: -	for_each_possible_cpu(i) -		kfree(all_cpu_data[i]); +	for_each_possible_cpu(i) { +		cpu = all_cpu_data[i]; +		if (!cpu) +			break; +		free_cpumask_var(cpu->shared_cpu_map); +		kfree(cpu); +	}  	kfree(all_cpu_data);  	return -ENODEV;  |