209 lines
4.4 KiB
C
209 lines
4.4 KiB
C
#include <linux/version.h>
|
|
#include <linux/module.h>
|
|
#if defined(MODVERSIONS)
|
|
#include <linux/modversions.h>
|
|
#endif
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/wait.h>
|
|
#include <asm/arch/kdpmd.h>
|
|
#include <asm/arch/pd.h>
|
|
|
|
#define _DEBUG 1
|
|
//#undef _DEBUG
|
|
|
|
#ifdef _DEBUG
|
|
#include <linux/time.h>
|
|
#endif
|
|
|
|
/* global variables for kdpmd */
|
|
DECLARE_MUTEX(kdpmd_sem);
|
|
unsigned int kdpmd_ev;
|
|
wait_queue_head_t kdpmd_wq;
|
|
unsigned int dd_ev;
|
|
wait_queue_head_t dd_wq;
|
|
unsigned int wakeup_source;
|
|
|
|
struct task_struct *kp;
|
|
|
|
void handle_timeout(void)
|
|
{
|
|
printk(KERN_INFO "kdpmd timeout\n");
|
|
/* dvfs code should go here */
|
|
}
|
|
|
|
/* this function should be called before device drivers turn on
|
|
their clocks */
|
|
void handle_drvopen(struct pm_pdtype *pd)
|
|
{
|
|
if (pd->state == PDSTATE_OFF) {
|
|
printk("Turning on %s pd\n", pd->name);
|
|
/* power on and device initializaion */
|
|
pd_on(pd);
|
|
}
|
|
/* clock control is done by the device driver */
|
|
}
|
|
|
|
/* this function should be called after device drivers turn off
|
|
their clocks and update pm_devtype->state value
|
|
|
|
If this function takes too much time, we may employ separate
|
|
lists for RUNNING devices and IDLE devices in a power domain */
|
|
void handle_drvclose(struct pm_pdtype *pd)
|
|
{
|
|
struct list_head *temp;
|
|
struct pm_devtype *pdev;
|
|
|
|
/* check this power domain */
|
|
list_for_each(temp, &pd->devhead) {
|
|
pdev = list_entry(temp, struct pm_devtype, entry);
|
|
if (pdev->state == DEV_RUNNING)
|
|
return;
|
|
}
|
|
/* if all devices under this power domain are not RUNNING */
|
|
pd_off(pd);
|
|
}
|
|
|
|
/* this is the thread function that we are executing */
|
|
static int kdpmd_thread(void *data)
|
|
{
|
|
struct pm_pdtype *pd;
|
|
unsigned int i = 0;
|
|
#ifdef _DEBUG
|
|
struct timeval tv1, tv2;
|
|
#endif
|
|
|
|
printk("Kernel DPM daemon thread started\n");
|
|
|
|
init_waitqueue_head(&kdpmd_wq);
|
|
init_waitqueue_head(&dd_wq);
|
|
|
|
/* an endless loop in which we are doing our work */
|
|
for (;;) {
|
|
i++;
|
|
/* fall asleep, wait for other processes to write to kdpmd_ev */
|
|
if (wait_event_interruptible(kdpmd_wq, kdpmd_ev != 0) < 0) {
|
|
printk(KERN_INFO "kdpmd wait returned -\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
do_gettimeofday(&tv1);
|
|
#endif
|
|
|
|
/* What if some other device opens up while servicing other */
|
|
kdpmd_lock();
|
|
|
|
/* We need to do a memory barrier here to be sure that
|
|
the flags are visible on all CPUs.
|
|
*/
|
|
mb();
|
|
|
|
/* here we are back from sleep, either due to the timeout
|
|
(2 seconds), or because we caught a signal.
|
|
*/
|
|
if (kthread_should_stop())
|
|
break;
|
|
|
|
printk(KERN_INFO "kdpmd woke up: %3d\n", i);
|
|
|
|
/* find out who woke me up */
|
|
pd = pd_hashtbl[kdpmd_ev];
|
|
|
|
/* find out why woke me up */
|
|
switch (wakeup_source) {
|
|
case KDPMD_TIMEOUT :
|
|
handle_timeout();
|
|
goto sleepagain;
|
|
case KDPMD_DRVOPEN :
|
|
handle_drvopen(pd);
|
|
break;
|
|
case KDPMD_DRVCLOSE :
|
|
handle_drvclose(pd);
|
|
break;
|
|
case KDPMD_REMOVE : // handle rmmod call
|
|
/* wait for 2 sec hoping that kthread_should_stop()
|
|
will evaluate to true */
|
|
wait_event_timeout(kdpmd_wq, kdpmd_ev != 0, 2 * HZ);
|
|
goto sleepagain;
|
|
default :
|
|
break;
|
|
}
|
|
dd_ev = kdpmd_ev;
|
|
wake_up_interruptible(&dd_wq);
|
|
sleepagain:
|
|
kdpmd_ev = 0;
|
|
kdpmd_unlock();
|
|
|
|
#ifdef _DEBUG
|
|
do_gettimeofday(&tv2);
|
|
printk(KERN_DEBUG " %d sec %d usec\n\n",
|
|
(int)tv2.tv_sec - (int)tv1.tv_sec,
|
|
(int)tv2.tv_usec - (int)tv1.tv_usec);
|
|
#endif
|
|
}
|
|
/* here we go only in case of termination of the thread */
|
|
/* returning from the thread here calls the exit functions */
|
|
return (0);
|
|
}
|
|
|
|
void kdpmd_set_event(unsigned int devid, unsigned int drv_op)
|
|
{
|
|
kdpmd_lock();
|
|
wakeup_source = drv_op;
|
|
kdpmd_ev = devid;
|
|
kdpmd_unlock();
|
|
}
|
|
|
|
void kdpmd_wakeup(void)
|
|
{
|
|
wake_up_interruptible(&kdpmd_wq);
|
|
}
|
|
|
|
int kdpmd_wait(unsigned int devid)
|
|
{
|
|
int ret;
|
|
|
|
ret = wait_event_interruptible(dd_wq, dd_ev==devid);
|
|
dd_ev = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __init kdpmd_init(void)
|
|
{
|
|
init_MUTEX(&kdpmd_sem);
|
|
kp = kthread_run(kdpmd_thread, NULL, "kdpmd");
|
|
printk(KERN_INFO "kdpmd created\n");
|
|
|
|
return (0);
|
|
}
|
|
|
|
void __exit kdpmd_exit(void)
|
|
{
|
|
printk(KERN_INFO "kdpmd being destroyed\n");
|
|
kdpmd_set_event(KDPMD_REMOVE, KDPMD_REMOVE);
|
|
kdpmd_wakeup();
|
|
/* terminate the kernel thread */
|
|
kthread_stop(kp);
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef MODULE
|
|
module_init(kdpmd_init);
|
|
module_exit(kdpmd_exit);
|
|
#else
|
|
__initcall(kdpmd_init);
|
|
#endif
|
|
|
|
EXPORT_SYMBOL(kdpmd_set_event);
|
|
EXPORT_SYMBOL(kdpmd_wakeup);
|
|
EXPORT_SYMBOL(kdpmd_wait);
|
|
|
|
MODULE_AUTHOR("Ikhwan Lee");
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
|