| 
									
										
										
										
											2008-07-16 05:51:43 +10:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * spu aware cpufreq governor for the cell processor | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * © Copyright IBM Corporation 2006-2008 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Author: Christian Krafft <krafft@de.ibm.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  * the Free Software Foundation; either version 2, or (at your option) | 
					
						
							|  |  |  |  * any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  |  * along with this program; if not, write to the Free Software | 
					
						
							|  |  |  |  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/cpufreq.h>
 | 
					
						
							|  |  |  | #include <linux/sched.h>
 | 
					
						
							|  |  |  | #include <linux/timer.h>
 | 
					
						
							|  |  |  | #include <linux/workqueue.h>
 | 
					
						
							|  |  |  | #include <asm/atomic.h>
 | 
					
						
							|  |  |  | #include <asm/machdep.h>
 | 
					
						
							|  |  |  | #include <asm/spu.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define POLL_TIME	100000		/* in µs */
 | 
					
						
							|  |  |  | #define EXP		753		/* exp(-1) in fixed-point */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct spu_gov_info_struct { | 
					
						
							|  |  |  | 	unsigned long busy_spus;	/* fixed-point */ | 
					
						
							|  |  |  | 	struct cpufreq_policy *policy; | 
					
						
							|  |  |  | 	struct delayed_work work; | 
					
						
							|  |  |  | 	unsigned int poll_int;		/* µs */ | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | static DEFINE_PER_CPU(struct spu_gov_info_struct, spu_gov_info); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct workqueue_struct *kspugov_wq; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int calc_freq(struct spu_gov_info_struct *info) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int cpu; | 
					
						
							|  |  |  | 	int busy_spus; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cpu = info->policy->cpu; | 
					
						
							|  |  |  | 	busy_spus = atomic_read(&cbe_spu_info[cpu_to_node(cpu)].busy_spus); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	CALC_LOAD(info->busy_spus, EXP, busy_spus * FIXED_1); | 
					
						
							|  |  |  | 	pr_debug("cpu %d: busy_spus=%d, info->busy_spus=%ld\n", | 
					
						
							|  |  |  | 			cpu, busy_spus, info->busy_spus); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return info->policy->max * info->busy_spus / FIXED_1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void spu_gov_work(struct work_struct *work) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct spu_gov_info_struct *info; | 
					
						
							|  |  |  | 	int delay; | 
					
						
							|  |  |  | 	unsigned long target_freq; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	info = container_of(work, struct spu_gov_info_struct, work.work); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* after cancel_delayed_work_sync we unset info->policy */ | 
					
						
							|  |  |  | 	BUG_ON(info->policy == NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	target_freq = calc_freq(info); | 
					
						
							|  |  |  | 	__cpufreq_driver_target(info->policy, target_freq, CPUFREQ_RELATION_H); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	delay = usecs_to_jiffies(info->poll_int); | 
					
						
							|  |  |  | 	queue_delayed_work_on(info->policy->cpu, kspugov_wq, &info->work, delay); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void spu_gov_init_work(struct spu_gov_info_struct *info) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int delay = usecs_to_jiffies(info->poll_int); | 
					
						
							|  |  |  | 	INIT_DELAYED_WORK_DEFERRABLE(&info->work, spu_gov_work); | 
					
						
							|  |  |  | 	queue_delayed_work_on(info->policy->cpu, kspugov_wq, &info->work, delay); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void spu_gov_cancel_work(struct spu_gov_info_struct *info) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	cancel_delayed_work_sync(&info->work); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int spu_gov_govern(struct cpufreq_policy *policy, unsigned int event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned int cpu = policy->cpu; | 
					
						
							|  |  |  | 	struct spu_gov_info_struct *info, *affected_info; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	info = &per_cpu(spu_gov_info, cpu); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (event) { | 
					
						
							|  |  |  | 	case CPUFREQ_GOV_START: | 
					
						
							|  |  |  | 		if (!cpu_online(cpu)) { | 
					
						
							|  |  |  | 			printk(KERN_ERR "cpu %d is not online\n", cpu); | 
					
						
							|  |  |  | 			ret = -EINVAL; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!policy->cur) { | 
					
						
							|  |  |  | 			printk(KERN_ERR "no cpu specified in policy\n"); | 
					
						
							|  |  |  | 			ret = -EINVAL; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* initialize spu_gov_info for all affected cpus */ | 
					
						
							| 
									
										
										
										
											2009-01-12 11:22:01 +11:00
										 |  |  | 		for_each_cpu(i, policy->cpus) { | 
					
						
							| 
									
										
										
										
											2008-07-16 05:51:43 +10:00
										 |  |  | 			affected_info = &per_cpu(spu_gov_info, i); | 
					
						
							|  |  |  | 			affected_info->policy = policy; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		info->poll_int = POLL_TIME; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* setup timer */ | 
					
						
							|  |  |  | 		spu_gov_init_work(info); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CPUFREQ_GOV_STOP: | 
					
						
							|  |  |  | 		/* cancel timer */ | 
					
						
							|  |  |  | 		spu_gov_cancel_work(info); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* clean spu_gov_info for all affected cpus */ | 
					
						
							| 
									
										
										
										
											2009-01-12 11:22:01 +11:00
										 |  |  | 		for_each_cpu (i, policy->cpus) { | 
					
						
							| 
									
										
										
										
											2008-07-16 05:51:43 +10:00
										 |  |  | 			info = &per_cpu(spu_gov_info, i); | 
					
						
							|  |  |  | 			info->policy = NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct cpufreq_governor spu_governor = { | 
					
						
							|  |  |  | 	.name = "spudemand", | 
					
						
							|  |  |  | 	.governor = spu_gov_govern, | 
					
						
							|  |  |  | 	.owner = THIS_MODULE, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * module init and destoy | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init spu_gov_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	kspugov_wq = create_workqueue("kspugov"); | 
					
						
							|  |  |  | 	if (!kspugov_wq) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "creation of kspugov failed\n"); | 
					
						
							|  |  |  | 		ret = -EFAULT; | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = cpufreq_register_governor(&spu_governor); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "registration of governor failed\n"); | 
					
						
							|  |  |  | 		destroy_workqueue(kspugov_wq); | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | out: | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void __exit spu_gov_exit(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	cpufreq_unregister_governor(&spu_governor); | 
					
						
							|  |  |  | 	destroy_workqueue(kspugov_wq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module_init(spu_gov_init); | 
					
						
							|  |  |  | module_exit(spu_gov_exit); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_LICENSE("GPL"); | 
					
						
							|  |  |  | MODULE_AUTHOR("Christian Krafft <krafft@de.ibm.com>"); | 
					
						
							|  |  |  | 
 |