PM / devfreq: exynos4: use common PPMU code
This patch converts exynos4_bus driver to use common PPMU code (exynos_ppmu.c) instead of individual functions related to PPC because PPMU is integrated module with both PPC and Bus event generator. When using PPMU to get bus performance read/write event exynos4_bus driver deson't need to consider memory type. Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com> [bzolnier: splitted out changes from the bigger patch] Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
This commit is contained in:
parent
ae29fa1d50
commit
ba778b374d
|
@ -1,3 +1,3 @@
|
||||||
# Exynos DEVFREQ Drivers
|
# Exynos DEVFREQ Drivers
|
||||||
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o
|
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos_ppmu.o exynos4_bus.o
|
||||||
obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_bus.o
|
obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_bus.o
|
||||||
|
|
|
@ -25,15 +25,16 @@
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
|
||||||
|
#include <mach/map.h>
|
||||||
|
|
||||||
|
#include "exynos_ppmu.h"
|
||||||
|
#include "exynos4_bus.h"
|
||||||
|
|
||||||
/* Exynos4 ASV has been in the mailing list, but not upstreamed, yet. */
|
/* Exynos4 ASV has been in the mailing list, but not upstreamed, yet. */
|
||||||
#ifdef CONFIG_EXYNOS_ASV
|
#ifdef CONFIG_EXYNOS_ASV
|
||||||
extern unsigned int exynos_result_of_asv;
|
extern unsigned int exynos_result_of_asv;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <mach/map.h>
|
|
||||||
|
|
||||||
#include "exynos4_bus.h"
|
|
||||||
|
|
||||||
#define MAX_SAFEVOLT 1200000 /* 1.2V */
|
#define MAX_SAFEVOLT 1200000 /* 1.2V */
|
||||||
|
|
||||||
enum exynos4_busf_type {
|
enum exynos4_busf_type {
|
||||||
|
@ -44,22 +45,6 @@ enum exynos4_busf_type {
|
||||||
/* Assume that the bus is saturated if the utilization is 40% */
|
/* Assume that the bus is saturated if the utilization is 40% */
|
||||||
#define BUS_SATURATION_RATIO 40
|
#define BUS_SATURATION_RATIO 40
|
||||||
|
|
||||||
enum ppmu_counter {
|
|
||||||
PPMU_PMNCNT0 = 0,
|
|
||||||
PPMU_PMCCNT1,
|
|
||||||
PPMU_PMNCNT2,
|
|
||||||
PPMU_PMNCNT3,
|
|
||||||
PPMU_PMNCNT_MAX,
|
|
||||||
};
|
|
||||||
struct exynos4_ppmu {
|
|
||||||
void __iomem *hw_base;
|
|
||||||
unsigned int ccnt;
|
|
||||||
unsigned int event;
|
|
||||||
unsigned int count[PPMU_PMNCNT_MAX];
|
|
||||||
bool ccnt_overflow;
|
|
||||||
bool count_overflow[PPMU_PMNCNT_MAX];
|
|
||||||
};
|
|
||||||
|
|
||||||
enum busclk_level_idx {
|
enum busclk_level_idx {
|
||||||
LV_0 = 0,
|
LV_0 = 0,
|
||||||
LV_1,
|
LV_1,
|
||||||
|
@ -68,6 +53,13 @@ enum busclk_level_idx {
|
||||||
LV_4,
|
LV_4,
|
||||||
_LV_END
|
_LV_END
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum exynos_ppmu_idx {
|
||||||
|
PPMU_DMC0,
|
||||||
|
PPMU_DMC1,
|
||||||
|
PPMU_END,
|
||||||
|
};
|
||||||
|
|
||||||
#define EX4210_LV_MAX LV_2
|
#define EX4210_LV_MAX LV_2
|
||||||
#define EX4x12_LV_MAX LV_4
|
#define EX4x12_LV_MAX LV_4
|
||||||
#define EX4210_LV_NUM (LV_2 + 1)
|
#define EX4210_LV_NUM (LV_2 + 1)
|
||||||
|
@ -91,7 +83,7 @@ struct busfreq_data {
|
||||||
struct regulator *vdd_int;
|
struct regulator *vdd_int;
|
||||||
struct regulator *vdd_mif; /* Exynos4412/4212 only */
|
struct regulator *vdd_mif; /* Exynos4412/4212 only */
|
||||||
struct busfreq_opp_info curr_oppinfo;
|
struct busfreq_opp_info curr_oppinfo;
|
||||||
struct exynos4_ppmu dmc[2];
|
struct exynos_ppmu ppmu[PPMU_END];
|
||||||
|
|
||||||
struct notifier_block pm_notifier;
|
struct notifier_block pm_notifier;
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
|
@ -101,12 +93,6 @@ struct busfreq_data {
|
||||||
unsigned int top_divtable[_LV_END];
|
unsigned int top_divtable[_LV_END];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct bus_opp_table {
|
|
||||||
unsigned int idx;
|
|
||||||
unsigned long clk;
|
|
||||||
unsigned long volt;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 4210 controls clock of mif and voltage of int */
|
/* 4210 controls clock of mif and voltage of int */
|
||||||
static struct bus_opp_table exynos4210_busclk_table[] = {
|
static struct bus_opp_table exynos4210_busclk_table[] = {
|
||||||
{LV_0, 400000, 1150000},
|
{LV_0, 400000, 1150000},
|
||||||
|
@ -524,27 +510,22 @@ static int exynos4x12_set_busclk(struct busfreq_data *data,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void busfreq_mon_reset(struct busfreq_data *data)
|
static void busfreq_mon_reset(struct busfreq_data *data)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
for (i = 0; i < 2; i++) {
|
for (i = 0; i < PPMU_END; i++) {
|
||||||
void __iomem *ppmu_base = data->dmc[i].hw_base;
|
void __iomem *ppmu_base = data->ppmu[i].hw_base;
|
||||||
|
|
||||||
/* Reset PPMU */
|
/* Reset the performance and cycle counters */
|
||||||
__raw_writel(0x8000000f, ppmu_base + 0xf010);
|
exynos_ppmu_reset(ppmu_base);
|
||||||
__raw_writel(0x8000000f, ppmu_base + 0xf050);
|
|
||||||
__raw_writel(0x6, ppmu_base + 0xf000);
|
|
||||||
__raw_writel(0x0, ppmu_base + 0xf100);
|
|
||||||
|
|
||||||
/* Set PPMU Event */
|
/* Setup count registers to monitor read/write transactions */
|
||||||
data->dmc[i].event = 0x6;
|
data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
|
||||||
__raw_writel(((data->dmc[i].event << 12) | 0x1),
|
exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
|
||||||
ppmu_base + 0xfc);
|
data->ppmu[i].event[PPMU_PMNCNT3]);
|
||||||
|
|
||||||
/* Start PPMU */
|
exynos_ppmu_start(ppmu_base);
|
||||||
__raw_writel(0x1, ppmu_base + 0xf000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,23 +533,20 @@ static void exynos4_read_ppmu(struct busfreq_data *data)
|
||||||
{
|
{
|
||||||
int i, j;
|
int i, j;
|
||||||
|
|
||||||
for (i = 0; i < 2; i++) {
|
for (i = 0; i < PPMU_END; i++) {
|
||||||
void __iomem *ppmu_base = data->dmc[i].hw_base;
|
void __iomem *ppmu_base = data->ppmu[i].hw_base;
|
||||||
u32 overflow;
|
|
||||||
|
|
||||||
/* Stop PPMU */
|
exynos_ppmu_stop(ppmu_base);
|
||||||
__raw_writel(0x0, ppmu_base + 0xf000);
|
|
||||||
|
|
||||||
/* Update local data from PPMU */
|
/* Update local data from PPMU */
|
||||||
overflow = __raw_readl(ppmu_base + 0xf050);
|
data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
|
||||||
|
|
||||||
data->dmc[i].ccnt = __raw_readl(ppmu_base + 0xf100);
|
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
|
||||||
data->dmc[i].ccnt_overflow = overflow & (1 << 31);
|
if (data->ppmu[i].event[j] == 0)
|
||||||
|
data->ppmu[i].count[j] = 0;
|
||||||
for (j = 0; j < PPMU_PMNCNT_MAX; j++) {
|
else
|
||||||
data->dmc[i].count[j] = __raw_readl(
|
data->ppmu[i].count[j] =
|
||||||
ppmu_base + (0xf110 + (0x10 * j)));
|
exynos_ppmu_read(ppmu_base, j);
|
||||||
data->dmc[i].count_overflow[j] = overflow & (1 << j);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -698,66 +676,42 @@ out:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int exynos4_get_busier_dmc(struct busfreq_data *data)
|
static int exynos4_get_busier_ppmu(struct busfreq_data *data)
|
||||||
{
|
{
|
||||||
u64 p0 = data->dmc[0].count[0];
|
int i, j;
|
||||||
u64 p1 = data->dmc[1].count[0];
|
int busy = 0;
|
||||||
|
unsigned int temp = 0;
|
||||||
|
|
||||||
p0 *= data->dmc[1].ccnt;
|
for (i = 0; i < PPMU_END; i++) {
|
||||||
p1 *= data->dmc[0].ccnt;
|
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
|
||||||
|
if (data->ppmu[i].count[j] > temp) {
|
||||||
|
temp = data->ppmu[i].count[j];
|
||||||
|
busy = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data->dmc[1].ccnt == 0)
|
return busy;
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (p0 > p1)
|
|
||||||
return 0;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int exynos4_bus_get_dev_status(struct device *dev,
|
static int exynos4_bus_get_dev_status(struct device *dev,
|
||||||
struct devfreq_dev_status *stat)
|
struct devfreq_dev_status *stat)
|
||||||
{
|
{
|
||||||
struct busfreq_data *data = dev_get_drvdata(dev);
|
struct busfreq_data *data = dev_get_drvdata(dev);
|
||||||
int busier_dmc;
|
int busier;
|
||||||
int cycles_x2 = 2; /* 2 x cycles */
|
|
||||||
void __iomem *addr;
|
|
||||||
u32 timing;
|
|
||||||
u32 memctrl;
|
|
||||||
|
|
||||||
exynos4_read_ppmu(data);
|
exynos4_read_ppmu(data);
|
||||||
busier_dmc = exynos4_get_busier_dmc(data);
|
busier = exynos4_get_busier_ppmu(data);
|
||||||
stat->current_frequency = data->curr_oppinfo.rate;
|
stat->current_frequency = data->curr_oppinfo.rate;
|
||||||
|
|
||||||
if (busier_dmc)
|
|
||||||
addr = S5P_VA_DMC1;
|
|
||||||
else
|
|
||||||
addr = S5P_VA_DMC0;
|
|
||||||
|
|
||||||
memctrl = __raw_readl(addr + 0x04); /* one of DDR2/3/LPDDR2 */
|
|
||||||
timing = __raw_readl(addr + 0x38); /* CL or WL/RL values */
|
|
||||||
|
|
||||||
switch ((memctrl >> 8) & 0xf) {
|
|
||||||
case 0x4: /* DDR2 */
|
|
||||||
cycles_x2 = ((timing >> 16) & 0xf) * 2;
|
|
||||||
break;
|
|
||||||
case 0x5: /* LPDDR2 */
|
|
||||||
case 0x6: /* DDR3 */
|
|
||||||
cycles_x2 = ((timing >> 8) & 0xf) + ((timing >> 0) & 0xf);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
pr_err("%s: Unknown Memory Type(%d).\n", __func__,
|
|
||||||
(memctrl >> 8) & 0xf);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Number of cycles spent on memory access */
|
/* Number of cycles spent on memory access */
|
||||||
stat->busy_time = data->dmc[busier_dmc].count[0] / 2 * (cycles_x2 + 2);
|
stat->busy_time = data->ppmu[busier].count[PPMU_PMNCNT3];
|
||||||
stat->busy_time *= 100 / BUS_SATURATION_RATIO;
|
stat->busy_time *= 100 / BUS_SATURATION_RATIO;
|
||||||
stat->total_time = data->dmc[busier_dmc].ccnt;
|
stat->total_time = data->ppmu[busier].ccnt;
|
||||||
|
|
||||||
/* If the counters have overflown, retry */
|
/* If the counters have overflown, retry */
|
||||||
if (data->dmc[busier_dmc].ccnt_overflow ||
|
if (data->ppmu[busier].ccnt_overflow ||
|
||||||
data->dmc[busier_dmc].count_overflow[0])
|
data->ppmu[busier].count_overflow[0])
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1023,8 +977,8 @@ static int exynos4_busfreq_probe(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
data->type = pdev->id_entry->driver_data;
|
data->type = pdev->id_entry->driver_data;
|
||||||
data->dmc[0].hw_base = S5P_VA_DMC0;
|
data->ppmu[PPMU_DMC0].hw_base = S5P_VA_DMC0;
|
||||||
data->dmc[1].hw_base = S5P_VA_DMC1;
|
data->ppmu[PPMU_DMC1].hw_base = S5P_VA_DMC1;
|
||||||
data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event;
|
data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event;
|
||||||
data->dev = dev;
|
data->dev = dev;
|
||||||
mutex_init(&data->lock);
|
mutex_init(&data->lock);
|
||||||
|
@ -1074,13 +1028,17 @@ static int exynos4_busfreq_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
platform_set_drvdata(pdev, data);
|
platform_set_drvdata(pdev, data);
|
||||||
|
|
||||||
busfreq_mon_reset(data);
|
|
||||||
|
|
||||||
data->devfreq = devfreq_add_device(dev, &exynos4_devfreq_profile,
|
data->devfreq = devfreq_add_device(dev, &exynos4_devfreq_profile,
|
||||||
"simple_ondemand", NULL);
|
"simple_ondemand", NULL);
|
||||||
if (IS_ERR(data->devfreq))
|
if (IS_ERR(data->devfreq))
|
||||||
return PTR_ERR(data->devfreq);
|
return PTR_ERR(data->devfreq);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start PPMU (Performance Profiling Monitoring Unit) to check
|
||||||
|
* utilization of each IP in the Exynos4 SoC.
|
||||||
|
*/
|
||||||
|
busfreq_mon_reset(data);
|
||||||
|
|
||||||
/* Register opp_notifier for Exynos4 busfreq */
|
/* Register opp_notifier for Exynos4 busfreq */
|
||||||
err = devfreq_register_opp_notifier(dev, data->devfreq);
|
err = devfreq_register_opp_notifier(dev, data->devfreq);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
|
Loading…
Reference in New Issue