blob: 5919d0a10b1105f23a2cc3ab57498d99e5183d3f [file] [log] [blame]
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/opp.h>
#include <linux/devfreq.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/jiffies.h>
#include <linux/clk.h>
#include <linux/c2k-devfreq.h>
//#define C2K_DEVFREQ_DEBUG
int devfreq_counters_init(struct c2k_devfreq_data *data)
{
devfreq_counters **dc = &data->dpc;
*dc = kzalloc(sizeof (devfreq_counters), GFP_KERNEL);
if (dc == NULL) {
printk ("%s: Cannot allocate memory for devfreq_counters.\n"\
, __func__);
return -ENOMEM;
}
#ifdef C2K_DEVFREQ_USE_KTIME
(*dc)->start_time = ktime_get();
#else
(*dc)->fentry_time = 0UL;
(*dc)->prev_count = 0UL;
(*dc)->busy_time = 0UL;
(*dc)->start_time = jiffies;
#endif
return 0;
}
EXPORT_SYMBOL(devfreq_counters_init);
static int c2k_devfreq_pm_notifier_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct c2k_devfreq_data *data = container_of(&this, struct c2k_devfreq_data,
pm_notifier);
switch (event) {
case PM_SUSPEND_PREPARE:
/* Deactivate DVFS */
mutex_lock(&data->lock);
data->disabled = true;
mutex_unlock(&data->lock);
return NOTIFY_OK;
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
/* Reactivate */
mutex_lock(&data->lock);
data->disabled = false;
mutex_unlock(&data->lock);
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
/*
* @devfreq_target Returns desired operating frequency for the device.
* Basically, get_target_freq will run
* devfreq_dev_profile.get_dev_status() to get the
* status of the device (load = busy_time / total_time).
*/
static int devfreq_target(struct device *dev, unsigned long *freq)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
struct c2k_devfreq_data *data = platform_get_drvdata(pdev);
unsigned long old_freq = opp_get_freq(data->curr_opp);
struct opp *new_opp;
int err = 0;
mutex_lock(&data->lock);
#ifdef C2K_DEVFREQ_DEBUG
c2k_devfreq_debug ("%s:current opp <f=%lu v=%lu>\n", __func__, \
opp_get_freq(data->curr_opp), opp_get_voltage(data->curr_opp));
#endif
if (data->disabled)
goto out;
/* here...find corresponding OPP w.r.t |_*freq_| */
new_opp = opp_find_freq_floor(dev, freq);
if (IS_ERR(new_opp)) {
printk("%s: Invalid frequency %lu kHz.\n",
__func__, *freq);
err = PTR_ERR(new_opp);
goto out;
}
#ifdef C2K_DEVFREQ_DEBUG
c2k_devfreq_debug ("%s:new opp <f=%lu v=%lu>\n", __func__, \
opp_get_freq(new_opp), opp_get_voltage(new_opp));
#endif
data->curr_opp = new_opp;
*freq = opp_get_freq(new_opp);
/* now set freq */
if (old_freq != *freq)
{
if (*freq >= UINT_MAX)
*freq = data->max_freq;
if (*freq == 0)
*freq = data->min_freq;
err = data->set_freq(data, freq); /* uses clk struct */
}
else
goto out;
if (err)
goto out;
/* we could set voltage also */
out:
mutex_unlock(&data->lock);
return err;
}
/*
* @total_time The total time represented by this instance of
* devfreq_dev_status
* @busy_time The time that the device was working among the
* total_time.
*/
static int get_devfreq_status(struct device *dev, struct devfreq_dev_status *stat)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
struct c2k_devfreq_data *data = platform_get_drvdata(pdev);
devfreq_counters *dc = data->dpc;
stat->current_frequency = opp_get_freq(data->curr_opp);
/* Here goes a list of mechanisms to fetch busy/total time:
- Performance counters
- Measure the time between "operation start" and "operation end" and
accumulate the time (getting busy time. probably by ktime?)
- Measure the idle time (CPUIDLE/CPUFREQ does this)
- Count the number of operation and calculate the operational time
based on the number.
*/
stat->busy_time = (unsigned long)module_busy_time(dc);
stat->total_time = (unsigned long)module_busy_plus_not_busy_time(dc);
devfreq_reset_counters(dc);
return 0;
}
int c2k_driver_devfreq(struct device *dev, struct c2k_devfreq_data *data)
{
struct devfreq *ldevfreq;
struct opp *opp;
int i, err = 0;
BUG_ON(data==NULL);
#ifdef C2K_DEVFREQ_DEBUG
c2k_devfreq_debug("%s: initializing for dev %s\n", __func__, dev_name(dev));
#endif
if (!(data->vdd_int))
{
data->vdd_int = regulator_get(dev, "c2k_default_vcc");
if (IS_ERR(data->vdd_int)) {
printk ("%s: Cannot get the regulator \"vcc_c2k\"\n", __func__);
err = PTR_ERR(data->vdd_int);
goto err_regulator;
}
}
data->dev = dev;
mutex_init(&data->lock);
/* OPP Entries assumed to be setup by now */
if (data->opp_table)
{
for (i = 0; data->opp_table[i].idx != 0; i++) {
#ifdef C2K_DEVFREQ_DEBUG
c2k_devfreq_debug("%s: Adding opp entry: %ld Hz, %ld uvolt.\n",\
__func__, data->opp_table[i].freq, data->opp_table[i].volt);
#endif
err = opp_add(dev, data->opp_table[i].freq, data->opp_table[i].volt);
if (err) {
printk("%s: Cannot add opp entries: err = %d\n",__func__, err);
goto err_opp_add;
}
}
}
else
{
printk ("%s: Not using OPP framework.\n", __func__);
}
if (data->devfreq_profile->initial_freq > 0)
{
opp = opp_find_freq_floor(dev, &(data->devfreq_profile->initial_freq));
if (IS_ERR(opp)) {
printk("%s: Invalid initial frequency %lu kHz.\n",
__func__, data->devfreq_profile->initial_freq);
err = PTR_ERR(opp);
goto err_opp_add;
}
data->curr_opp = opp;
}
else
return -EINVAL;
if (!data->devfreq_profile->target)
data->devfreq_profile->target = devfreq_target; /* using default */
if (!data->devfreq_profile->get_dev_status)
data->devfreq_profile->get_dev_status = get_devfreq_status; /* using default */
if (!data->gov)
data->gov = &devfreq_simple_ondemand; /* using default governer */
dev_set_drvdata(dev, data);
ldevfreq = devfreq_add_device(dev, data->devfreq_profile, data->gov, NULL);
if (!ldevfreq)
{
printk ("%s: devfreq_add_device failed with err = %d\n", __func__, err);
goto err_opp_add;
}
if (data->pm_notifier)
{
err = register_pm_notifier(data->pm_notifier);
if (err) {
printk ("%s: Failed to setup pm notifier.\n", __func__);
goto err_notifier;
}
}
else
{
data->pm_notifier = (struct notifier_block*)kzalloc\
(sizeof(struct notifier_block), GFP_KERNEL);
if (data->pm_notifier == NULL) {
printk ("%s: Cannot allocate memory for pm_notifier.\n",\
__func__);
return -ENOMEM;
}
data->pm_notifier->notifier_call = c2k_devfreq_pm_notifier_event; /* default pm notifier */
err = register_pm_notifier(data->pm_notifier);
if (err) {
printk ("%s: Failed to setup pm notifier.\n", __func__);
goto err_notifier;
}
}
err = devfreq_counters_init(data);
return err;
err_notifier:
devfreq_remove_device(ldevfreq);
err_opp_add:
regulator_put(data->vdd_int);
err_regulator:
kfree(data);
return err;
}
EXPORT_SYMBOL(c2k_driver_devfreq);