Merge branch 'pm-devfreq'
* pm-devfreq: MAINTAINERS: update mailing list for devfreq(DVFS). PM / devfreq: fix typo "CPU_EXYNOS4.12" twice PM / devfreq: fix missing unlock on error in exynos4_busfreq_pm_notifier_event() PM / devfreq: add comments and Documentation PM / devfreq: account suspend/resume for stats PM / devfreq: Add Exynos5-bus devfreq driver for Exynos5250 PM / devfreq: Move exynos4 devfreq driver into a new sub-directory
This commit is contained in:
commit
ceaed1e394
|
@ -78,3 +78,23 @@ Contact: Nishanth Menon <nm@ti.com>
|
|||
Description:
|
||||
The /sys/class/devfreq/.../available_governors shows
|
||||
currently available governors in the system.
|
||||
|
||||
What: /sys/class/devfreq/.../min_freq
|
||||
Date: January 2013
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
Description:
|
||||
The /sys/class/devfreq/.../min_freq shows and stores
|
||||
the minimum frequency requested by users. It is 0 if
|
||||
the user does not care. min_freq overrides the
|
||||
frequency requested by governors.
|
||||
|
||||
What: /sys/class/devfreq/.../max_freq
|
||||
Date: January 2013
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
Description:
|
||||
The /sys/class/devfreq/.../max_freq shows and stores
|
||||
the maximum frequency requested by users. It is 0 if
|
||||
the user does not care. max_freq overrides the
|
||||
frequency requested by governors and min_freq.
|
||||
The max_freq overrides min_freq because max_freq may be
|
||||
used to throttle devices to avoid overheating.
|
||||
|
|
|
@ -2519,7 +2519,7 @@ F: drivers/usb/dwc3/
|
|||
DEVICE FREQUENCY (DEVFREQ)
|
||||
M: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
M: Kyungmin Park <kyungmin.park@samsung.com>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
L: linux-pm@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/devfreq/
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ comment "DEVFREQ Drivers"
|
|||
|
||||
config ARM_EXYNOS4_BUS_DEVFREQ
|
||||
bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver"
|
||||
depends on CPU_EXYNOS4210 || CPU_EXYNOS4212 || CPU_EXYNOS4412
|
||||
depends on CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412
|
||||
select ARCH_HAS_OPP
|
||||
select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
||||
help
|
||||
|
@ -78,4 +78,14 @@ config ARM_EXYNOS4_BUS_DEVFREQ
|
|||
To operate with optimal voltages, ASV support is required
|
||||
(CONFIG_EXYNOS_ASV).
|
||||
|
||||
config ARM_EXYNOS5_BUS_DEVFREQ
|
||||
bool "ARM Exynos5250 Bus DEVFREQ Driver"
|
||||
depends on SOC_EXYNOS5250
|
||||
select ARCH_HAS_OPP
|
||||
select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
||||
help
|
||||
This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int).
|
||||
It reads PPMU counters of memory controllers and adjusts the
|
||||
operating frequencies and voltages with OPP support.
|
||||
|
||||
endif # PM_DEVFREQ
|
||||
|
|
|
@ -5,4 +5,5 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o
|
|||
obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
|
||||
|
||||
# DEVFREQ Drivers
|
||||
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o
|
||||
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/
|
||||
obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/
|
||||
|
|
|
@ -271,6 +271,7 @@ void devfreq_monitor_suspend(struct devfreq *devfreq)
|
|||
return;
|
||||
}
|
||||
|
||||
devfreq_update_status(devfreq, devfreq->previous_freq);
|
||||
devfreq->stop_polling = true;
|
||||
mutex_unlock(&devfreq->lock);
|
||||
cancel_delayed_work_sync(&devfreq->work);
|
||||
|
@ -287,6 +288,8 @@ EXPORT_SYMBOL(devfreq_monitor_suspend);
|
|||
*/
|
||||
void devfreq_monitor_resume(struct devfreq *devfreq)
|
||||
{
|
||||
unsigned long freq;
|
||||
|
||||
mutex_lock(&devfreq->lock);
|
||||
if (!devfreq->stop_polling)
|
||||
goto out;
|
||||
|
@ -295,8 +298,14 @@ void devfreq_monitor_resume(struct devfreq *devfreq)
|
|||
devfreq->profile->polling_ms)
|
||||
queue_delayed_work(devfreq_wq, &devfreq->work,
|
||||
msecs_to_jiffies(devfreq->profile->polling_ms));
|
||||
|
||||
devfreq->last_stat_updated = jiffies;
|
||||
devfreq->stop_polling = false;
|
||||
|
||||
if (devfreq->profile->get_cur_freq &&
|
||||
!devfreq->profile->get_cur_freq(devfreq->dev.parent, &freq))
|
||||
devfreq->previous_freq = freq;
|
||||
|
||||
out:
|
||||
mutex_unlock(&devfreq->lock);
|
||||
}
|
||||
|
@ -518,6 +527,8 @@ EXPORT_SYMBOL(devfreq_add_device);
|
|||
/**
|
||||
* devfreq_remove_device() - Remove devfreq feature from a device.
|
||||
* @devfreq: the devfreq instance to be removed
|
||||
*
|
||||
* The opposite of devfreq_add_device().
|
||||
*/
|
||||
int devfreq_remove_device(struct devfreq *devfreq)
|
||||
{
|
||||
|
@ -533,6 +544,10 @@ EXPORT_SYMBOL(devfreq_remove_device);
|
|||
/**
|
||||
* devfreq_suspend_device() - Suspend devfreq of a device.
|
||||
* @devfreq: the devfreq instance to be suspended
|
||||
*
|
||||
* This function is intended to be called by the pm callbacks
|
||||
* (e.g., runtime_suspend, suspend) of the device driver that
|
||||
* holds the devfreq.
|
||||
*/
|
||||
int devfreq_suspend_device(struct devfreq *devfreq)
|
||||
{
|
||||
|
@ -550,6 +565,10 @@ EXPORT_SYMBOL(devfreq_suspend_device);
|
|||
/**
|
||||
* devfreq_resume_device() - Resume devfreq of a device.
|
||||
* @devfreq: the devfreq instance to be resumed
|
||||
*
|
||||
* This function is intended to be called by the pm callbacks
|
||||
* (e.g., runtime_resume, resume) of the device driver that
|
||||
* holds the devfreq.
|
||||
*/
|
||||
int devfreq_resume_device(struct devfreq *devfreq)
|
||||
{
|
||||
|
@ -905,11 +924,11 @@ static ssize_t show_trans_table(struct device *dev, struct device_attribute *att
|
|||
{
|
||||
struct devfreq *devfreq = to_devfreq(dev);
|
||||
ssize_t len;
|
||||
int i, j, err;
|
||||
int i, j;
|
||||
unsigned int max_state = devfreq->profile->max_state;
|
||||
|
||||
err = devfreq_update_status(devfreq, devfreq->previous_freq);
|
||||
if (err)
|
||||
if (!devfreq->stop_polling &&
|
||||
devfreq_update_status(devfreq, devfreq->previous_freq))
|
||||
return 0;
|
||||
|
||||
len = sprintf(buf, " From : To\n");
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Exynos DEVFREQ Drivers
|
||||
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o
|
||||
obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_bus.o
|
|
@ -974,6 +974,7 @@ static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this,
|
|||
rcu_read_unlock();
|
||||
dev_err(data->dev, "%s: unable to find a min freq\n",
|
||||
__func__);
|
||||
mutex_unlock(&data->lock);
|
||||
return PTR_ERR(opp);
|
||||
}
|
||||
new_oppinfo.rate = opp_get_freq(opp);
|
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com/
|
||||
*
|
||||
* EXYNOS5 INT clock frequency scaling support using DEVFREQ framework
|
||||
* Based on work done by Jonghwan Choi <jhbird.choi@samsung.com>
|
||||
* Support for only EXYNOS5250 is present.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/devfreq.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/opp.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/opp.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include "exynos_ppmu.h"
|
||||
|
||||
#define MAX_SAFEVOLT 1100000 /* 1.10V */
|
||||
/* Assume that the bus is saturated if the utilization is 25% */
|
||||
#define INT_BUS_SATURATION_RATIO 25
|
||||
|
||||
enum int_level_idx {
|
||||
LV_0,
|
||||
LV_1,
|
||||
LV_2,
|
||||
LV_3,
|
||||
LV_4,
|
||||
_LV_END
|
||||
};
|
||||
|
||||
enum exynos_ppmu_list {
|
||||
PPMU_RIGHT,
|
||||
PPMU_END,
|
||||
};
|
||||
|
||||
struct busfreq_data_int {
|
||||
struct device *dev;
|
||||
struct devfreq *devfreq;
|
||||
struct regulator *vdd_int;
|
||||
struct exynos_ppmu ppmu[PPMU_END];
|
||||
unsigned long curr_freq;
|
||||
bool disabled;
|
||||
|
||||
struct notifier_block pm_notifier;
|
||||
struct mutex lock;
|
||||
struct pm_qos_request int_req;
|
||||
struct clk *int_clk;
|
||||
};
|
||||
|
||||
struct int_bus_opp_table {
|
||||
unsigned int idx;
|
||||
unsigned long clk;
|
||||
unsigned long volt;
|
||||
};
|
||||
|
||||
static struct int_bus_opp_table exynos5_int_opp_table[] = {
|
||||
{LV_0, 266000, 1025000},
|
||||
{LV_1, 200000, 1025000},
|
||||
{LV_2, 160000, 1025000},
|
||||
{LV_3, 133000, 1025000},
|
||||
{LV_4, 100000, 1025000},
|
||||
{0, 0, 0},
|
||||
};
|
||||
|
||||
static void busfreq_mon_reset(struct busfreq_data_int *data)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = PPMU_RIGHT; i < PPMU_END; i++) {
|
||||
void __iomem *ppmu_base = data->ppmu[i].hw_base;
|
||||
|
||||
/* Reset the performance and cycle counters */
|
||||
exynos_ppmu_reset(ppmu_base);
|
||||
|
||||
/* Setup count registers to monitor read/write transactions */
|
||||
data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
|
||||
exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
|
||||
data->ppmu[i].event[PPMU_PMNCNT3]);
|
||||
|
||||
exynos_ppmu_start(ppmu_base);
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos5_read_ppmu(struct busfreq_data_int *data)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = PPMU_RIGHT; i < PPMU_END; i++) {
|
||||
void __iomem *ppmu_base = data->ppmu[i].hw_base;
|
||||
|
||||
exynos_ppmu_stop(ppmu_base);
|
||||
|
||||
/* Update local data from PPMU */
|
||||
data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
|
||||
|
||||
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
|
||||
if (data->ppmu[i].event[j] == 0)
|
||||
data->ppmu[i].count[j] = 0;
|
||||
else
|
||||
data->ppmu[i].count[j] =
|
||||
exynos_ppmu_read(ppmu_base, j);
|
||||
}
|
||||
}
|
||||
|
||||
busfreq_mon_reset(data);
|
||||
}
|
||||
|
||||
static int exynos5_int_setvolt(struct busfreq_data_int *data,
|
||||
unsigned long volt)
|
||||
{
|
||||
return regulator_set_voltage(data->vdd_int, volt, MAX_SAFEVOLT);
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq,
|
||||
u32 flags)
|
||||
{
|
||||
int err = 0;
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device,
|
||||
dev);
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
struct opp *opp;
|
||||
unsigned long old_freq, freq;
|
||||
unsigned long volt;
|
||||
|
||||
rcu_read_lock();
|
||||
opp = devfreq_recommended_opp(dev, _freq, flags);
|
||||
if (IS_ERR(opp)) {
|
||||
rcu_read_unlock();
|
||||
dev_err(dev, "%s: Invalid OPP.\n", __func__);
|
||||
return PTR_ERR(opp);
|
||||
}
|
||||
|
||||
freq = opp_get_freq(opp);
|
||||
volt = opp_get_voltage(opp);
|
||||
rcu_read_unlock();
|
||||
|
||||
old_freq = data->curr_freq;
|
||||
|
||||
if (old_freq == freq)
|
||||
return 0;
|
||||
|
||||
dev_dbg(dev, "targetting %lukHz %luuV\n", freq, volt);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
if (data->disabled)
|
||||
goto out;
|
||||
|
||||
if (freq > exynos5_int_opp_table[0].clk)
|
||||
pm_qos_update_request(&data->int_req, freq * 16 / 1000);
|
||||
else
|
||||
pm_qos_update_request(&data->int_req, -1);
|
||||
|
||||
if (old_freq < freq)
|
||||
err = exynos5_int_setvolt(data, volt);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = clk_set_rate(data->int_clk, freq * 1000);
|
||||
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (old_freq > freq)
|
||||
err = exynos5_int_setvolt(data, volt);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
data->curr_freq = freq;
|
||||
out:
|
||||
mutex_unlock(&data->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int exynos5_get_busier_dmc(struct busfreq_data_int *data)
|
||||
{
|
||||
int i, j;
|
||||
int busy = 0;
|
||||
unsigned int temp = 0;
|
||||
|
||||
for (i = PPMU_RIGHT; i < PPMU_END; i++) {
|
||||
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
|
||||
if (data->ppmu[i].count[j] > temp) {
|
||||
temp = data->ppmu[i].count[j];
|
||||
busy = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return busy;
|
||||
}
|
||||
|
||||
static int exynos5_int_get_dev_status(struct device *dev,
|
||||
struct devfreq_dev_status *stat)
|
||||
{
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device,
|
||||
dev);
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
int busier_dmc;
|
||||
|
||||
exynos5_read_ppmu(data);
|
||||
busier_dmc = exynos5_get_busier_dmc(data);
|
||||
|
||||
stat->current_frequency = data->curr_freq;
|
||||
|
||||
/* Number of cycles spent on memory access */
|
||||
stat->busy_time = data->ppmu[busier_dmc].count[PPMU_PMNCNT3];
|
||||
stat->busy_time *= 100 / INT_BUS_SATURATION_RATIO;
|
||||
stat->total_time = data->ppmu[busier_dmc].ccnt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
static void exynos5_int_exit(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device,
|
||||
dev);
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
|
||||
devfreq_unregister_opp_notifier(dev, data->devfreq);
|
||||
}
|
||||
|
||||
static struct devfreq_dev_profile exynos5_devfreq_int_profile = {
|
||||
.initial_freq = 160000,
|
||||
.polling_ms = 100,
|
||||
.target = exynos5_busfreq_int_target,
|
||||
.get_dev_status = exynos5_int_get_dev_status,
|
||||
.exit = exynos5_int_exit,
|
||||
};
|
||||
|
||||
static int exynos5250_init_int_tables(struct busfreq_data_int *data)
|
||||
{
|
||||
int i, err = 0;
|
||||
|
||||
for (i = LV_0; i < _LV_END; i++) {
|
||||
err = opp_add(data->dev, exynos5_int_opp_table[i].clk,
|
||||
exynos5_int_opp_table[i].volt);
|
||||
if (err) {
|
||||
dev_err(data->dev, "Cannot add opp entries.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct busfreq_data_int *data = container_of(this,
|
||||
struct busfreq_data_int, pm_notifier);
|
||||
struct opp *opp;
|
||||
unsigned long maxfreq = ULONG_MAX;
|
||||
unsigned long freq;
|
||||
unsigned long volt;
|
||||
int err = 0;
|
||||
|
||||
switch (event) {
|
||||
case PM_SUSPEND_PREPARE:
|
||||
/* Set Fastest and Deactivate DVFS */
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
data->disabled = true;
|
||||
|
||||
rcu_read_lock();
|
||||
opp = opp_find_freq_floor(data->dev, &maxfreq);
|
||||
if (IS_ERR(opp)) {
|
||||
rcu_read_unlock();
|
||||
err = PTR_ERR(opp);
|
||||
goto unlock;
|
||||
}
|
||||
freq = opp_get_freq(opp);
|
||||
volt = opp_get_voltage(opp);
|
||||
rcu_read_unlock();
|
||||
|
||||
err = exynos5_int_setvolt(data, volt);
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
err = clk_set_rate(data->int_clk, freq * 1000);
|
||||
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
data->curr_freq = freq;
|
||||
unlock:
|
||||
mutex_unlock(&data->lock);
|
||||
if (err)
|
||||
return NOTIFY_BAD;
|
||||
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;
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct busfreq_data_int *data;
|
||||
struct opp *opp;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np;
|
||||
unsigned long initial_freq;
|
||||
unsigned long initial_volt;
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_int),
|
||||
GFP_KERNEL);
|
||||
if (data == NULL) {
|
||||
dev_err(dev, "Cannot allocate memory.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
np = of_find_compatible_node(NULL, NULL, "samsung,exynos5250-ppmu");
|
||||
if (np == NULL) {
|
||||
pr_err("Unable to find PPMU node\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
for (i = PPMU_RIGHT; i < PPMU_END; i++) {
|
||||
/* map PPMU memory region */
|
||||
data->ppmu[i].hw_base = of_iomap(np, i);
|
||||
if (data->ppmu[i].hw_base == NULL) {
|
||||
dev_err(&pdev->dev, "failed to map memory region\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
data->pm_notifier.notifier_call = exynos5_busfreq_int_pm_notifier_event;
|
||||
data->dev = dev;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
err = exynos5250_init_int_tables(data);
|
||||
if (err)
|
||||
goto err_regulator;
|
||||
|
||||
data->vdd_int = regulator_get(dev, "vdd_int");
|
||||
if (IS_ERR(data->vdd_int)) {
|
||||
dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
|
||||
err = PTR_ERR(data->vdd_int);
|
||||
goto err_regulator;
|
||||
}
|
||||
|
||||
data->int_clk = clk_get(dev, "int_clk");
|
||||
if (IS_ERR(data->int_clk)) {
|
||||
dev_err(dev, "Cannot get clock \"int_clk\"\n");
|
||||
err = PTR_ERR(data->int_clk);
|
||||
goto err_clock;
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
opp = opp_find_freq_floor(dev,
|
||||
&exynos5_devfreq_int_profile.initial_freq);
|
||||
if (IS_ERR(opp)) {
|
||||
rcu_read_unlock();
|
||||
dev_err(dev, "Invalid initial frequency %lu kHz.\n",
|
||||
exynos5_devfreq_int_profile.initial_freq);
|
||||
err = PTR_ERR(opp);
|
||||
goto err_opp_add;
|
||||
}
|
||||
initial_freq = opp_get_freq(opp);
|
||||
initial_volt = opp_get_voltage(opp);
|
||||
rcu_read_unlock();
|
||||
data->curr_freq = initial_freq;
|
||||
|
||||
err = clk_set_rate(data->int_clk, initial_freq * 1000);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to set initial frequency\n");
|
||||
goto err_opp_add;
|
||||
}
|
||||
|
||||
err = exynos5_int_setvolt(data, initial_volt);
|
||||
if (err)
|
||||
goto err_opp_add;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
busfreq_mon_reset(data);
|
||||
|
||||
data->devfreq = devfreq_add_device(dev, &exynos5_devfreq_int_profile,
|
||||
"simple_ondemand", NULL);
|
||||
|
||||
if (IS_ERR(data->devfreq)) {
|
||||
err = PTR_ERR(data->devfreq);
|
||||
goto err_devfreq_add;
|
||||
}
|
||||
|
||||
devfreq_register_opp_notifier(dev, data->devfreq);
|
||||
|
||||
err = register_pm_notifier(&data->pm_notifier);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to setup pm notifier\n");
|
||||
goto err_devfreq_add;
|
||||
}
|
||||
|
||||
/* TODO: Add a new QOS class for int/mif bus */
|
||||
pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1);
|
||||
|
||||
return 0;
|
||||
|
||||
err_devfreq_add:
|
||||
devfreq_remove_device(data->devfreq);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
err_opp_add:
|
||||
clk_put(data->int_clk);
|
||||
err_clock:
|
||||
regulator_put(data->vdd_int);
|
||||
err_regulator:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
|
||||
pm_qos_remove_request(&data->int_req);
|
||||
unregister_pm_notifier(&data->pm_notifier);
|
||||
devfreq_remove_device(data->devfreq);
|
||||
regulator_put(data->vdd_int);
|
||||
clk_put(data->int_clk);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device,
|
||||
dev);
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
|
||||
busfreq_mon_reset(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops exynos5_busfreq_int_pm = {
|
||||
.resume = exynos5_busfreq_int_resume,
|
||||
};
|
||||
|
||||
/* platform device pointer for exynos5 devfreq device. */
|
||||
static struct platform_device *exynos5_devfreq_pdev;
|
||||
|
||||
static struct platform_driver exynos5_busfreq_int_driver = {
|
||||
.probe = exynos5_busfreq_int_probe,
|
||||
.remove = exynos5_busfreq_int_remove,
|
||||
.driver = {
|
||||
.name = "exynos5-bus-int",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &exynos5_busfreq_int_pm,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init exynos5_busfreq_int_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_register(&exynos5_busfreq_int_driver);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
exynos5_devfreq_pdev =
|
||||
platform_device_register_simple("exynos5-bus-int", -1, NULL, 0);
|
||||
if (IS_ERR_OR_NULL(exynos5_devfreq_pdev)) {
|
||||
ret = PTR_ERR(exynos5_devfreq_pdev);
|
||||
goto out1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out1:
|
||||
platform_driver_unregister(&exynos5_busfreq_int_driver);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
late_initcall(exynos5_busfreq_int_init);
|
||||
|
||||
static void __exit exynos5_busfreq_int_exit(void)
|
||||
{
|
||||
platform_device_unregister(exynos5_devfreq_pdev);
|
||||
platform_driver_unregister(&exynos5_busfreq_int_driver);
|
||||
}
|
||||
module_exit(exynos5_busfreq_int_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework");
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com/
|
||||
*
|
||||
* EXYNOS - PPMU support
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include "exynos_ppmu.h"
|
||||
|
||||
void exynos_ppmu_reset(void __iomem *ppmu_base)
|
||||
{
|
||||
__raw_writel(PPMU_CYCLE_RESET | PPMU_COUNTER_RESET, ppmu_base);
|
||||
__raw_writel(PPMU_ENABLE_CYCLE |
|
||||
PPMU_ENABLE_COUNT0 |
|
||||
PPMU_ENABLE_COUNT1 |
|
||||
PPMU_ENABLE_COUNT2 |
|
||||
PPMU_ENABLE_COUNT3,
|
||||
ppmu_base + PPMU_CNTENS);
|
||||
}
|
||||
|
||||
void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
|
||||
unsigned int evt)
|
||||
{
|
||||
__raw_writel(evt, ppmu_base + PPMU_BEVTSEL(ch));
|
||||
}
|
||||
|
||||
void exynos_ppmu_start(void __iomem *ppmu_base)
|
||||
{
|
||||
__raw_writel(PPMU_ENABLE, ppmu_base);
|
||||
}
|
||||
|
||||
void exynos_ppmu_stop(void __iomem *ppmu_base)
|
||||
{
|
||||
__raw_writel(PPMU_DISABLE, ppmu_base);
|
||||
}
|
||||
|
||||
unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch)
|
||||
{
|
||||
unsigned int total;
|
||||
|
||||
if (ch == PPMU_PMNCNT3)
|
||||
total = ((__raw_readl(ppmu_base + PMCNT_OFFSET(ch)) << 8) |
|
||||
__raw_readl(ppmu_base + PMCNT_OFFSET(ch + 1)));
|
||||
else
|
||||
total = __raw_readl(ppmu_base + PMCNT_OFFSET(ch));
|
||||
|
||||
return total;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com/
|
||||
*
|
||||
* EXYNOS PPMU header
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __DEVFREQ_EXYNOS_PPMU_H
|
||||
#define __DEVFREQ_EXYNOS_PPMU_H __FILE__
|
||||
|
||||
#include <linux/ktime.h>
|
||||
|
||||
/* For PPMU Control */
|
||||
#define PPMU_ENABLE BIT(0)
|
||||
#define PPMU_DISABLE 0x0
|
||||
#define PPMU_CYCLE_RESET BIT(1)
|
||||
#define PPMU_COUNTER_RESET BIT(2)
|
||||
|
||||
#define PPMU_ENABLE_COUNT0 BIT(0)
|
||||
#define PPMU_ENABLE_COUNT1 BIT(1)
|
||||
#define PPMU_ENABLE_COUNT2 BIT(2)
|
||||
#define PPMU_ENABLE_COUNT3 BIT(3)
|
||||
#define PPMU_ENABLE_CYCLE BIT(31)
|
||||
|
||||
#define PPMU_CNTENS 0x10
|
||||
#define PPMU_FLAG 0x50
|
||||
#define PPMU_CCNT_OVERFLOW BIT(31)
|
||||
#define PPMU_CCNT 0x100
|
||||
|
||||
#define PPMU_PMCNT0 0x110
|
||||
#define PPMU_PMCNT_OFFSET 0x10
|
||||
#define PMCNT_OFFSET(x) (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * x))
|
||||
|
||||
#define PPMU_BEVT0SEL 0x1000
|
||||
#define PPMU_BEVTSEL_OFFSET 0x100
|
||||
#define PPMU_BEVTSEL(x) (PPMU_BEVT0SEL + (ch * PPMU_BEVTSEL_OFFSET))
|
||||
|
||||
/* For Event Selection */
|
||||
#define RD_DATA_COUNT 0x5
|
||||
#define WR_DATA_COUNT 0x6
|
||||
#define RDWR_DATA_COUNT 0x7
|
||||
|
||||
enum ppmu_counter {
|
||||
PPMU_PMNCNT0,
|
||||
PPMU_PMCCNT1,
|
||||
PPMU_PMNCNT2,
|
||||
PPMU_PMNCNT3,
|
||||
PPMU_PMNCNT_MAX,
|
||||
};
|
||||
|
||||
struct bus_opp_table {
|
||||
unsigned int idx;
|
||||
unsigned long clk;
|
||||
unsigned long volt;
|
||||
};
|
||||
|
||||
struct exynos_ppmu {
|
||||
void __iomem *hw_base;
|
||||
unsigned int ccnt;
|
||||
unsigned int event[PPMU_PMNCNT_MAX];
|
||||
unsigned int count[PPMU_PMNCNT_MAX];
|
||||
unsigned long long ns;
|
||||
ktime_t reset_time;
|
||||
bool ccnt_overflow;
|
||||
bool count_overflow[PPMU_PMNCNT_MAX];
|
||||
};
|
||||
|
||||
void exynos_ppmu_reset(void __iomem *ppmu_base);
|
||||
void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
|
||||
unsigned int evt);
|
||||
void exynos_ppmu_start(void __iomem *ppmu_base);
|
||||
void exynos_ppmu_stop(void __iomem *ppmu_base);
|
||||
unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch);
|
||||
#endif /* __DEVFREQ_EXYNOS_PPMU_H */
|
|
@ -181,6 +181,8 @@ extern struct devfreq *devfreq_add_device(struct device *dev,
|
|||
const char *governor_name,
|
||||
void *data);
|
||||
extern int devfreq_remove_device(struct devfreq *devfreq);
|
||||
|
||||
/* Supposed to be called by PM_SLEEP/PM_RUNTIME callbacks */
|
||||
extern int devfreq_suspend_device(struct devfreq *devfreq);
|
||||
extern int devfreq_resume_device(struct devfreq *devfreq);
|
||||
|
||||
|
|
Loading…
Reference in New Issue