Merge branch 'pm-devfreq'
* pm-devfreq: PM / devfreq: remove checks for CONFIG_EXYNOS_ASV PM / devfreq: exynos5: Use devm_devfreq_* function using device resource management PM / devfreq: exynos4: Use devm_devfreq_* function using device resource management PM / devfreq: Add devm_devfreq_{register,unregister}_opp_notfier function PM / devfreq: Add resource-managed function for devfreq device PM / devfreq: Fix devfreq_remove_device() to improve the sequence of resource free PM / devfreq: exynos: make more PPMU code common PM / devfreq: exynos5: introduce struct busfreq_ppmu_data PM / devfreq: exynos4: introduce struct busfreq_ppmu_data PM / devfreq: exynos4: use common PPMU code PM / devfreq: exynos5: Add CONFIG_PM_OPP dependency to fix probe fail PM / devfreq: exynos5: Use SIMPLE_DEV_PM_OPS macro PM / devfreq: exynos4: Add CONFIG_PM_OPP dependency to fix probe fail PM / devfreq: exynos4: Use SIMPLE_DEV_PM_OPS macro PM / devfreq: exynos4: Fix bug of resource leak and code clean on probe()
This commit is contained in:
Коммит
42a09284fa
|
@ -70,19 +70,20 @@ config ARM_EXYNOS4_BUS_DEVFREQ
|
|||
depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM
|
||||
select ARCH_HAS_OPP
|
||||
select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
||||
select PM_OPP
|
||||
help
|
||||
This adds the DEVFREQ driver for Exynos4210 memory bus (vdd_int)
|
||||
and Exynos4212/4412 memory interface and bus (vdd_mif + vdd_int).
|
||||
It reads PPMU counters of memory controllers and adjusts
|
||||
the operating frequencies and voltages with OPP support.
|
||||
To operate with optimal voltages, ASV support is required
|
||||
(CONFIG_EXYNOS_ASV).
|
||||
This does not yet operate with optimal voltages.
|
||||
|
||||
config ARM_EXYNOS5_BUS_DEVFREQ
|
||||
bool "ARM Exynos5250 Bus DEVFREQ Driver"
|
||||
depends on SOC_EXYNOS5250
|
||||
select ARCH_HAS_OPP
|
||||
select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
||||
select PM_OPP
|
||||
help
|
||||
This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int).
|
||||
It reads PPMU counters of memory controllers and adjusts the
|
||||
|
|
|
@ -394,7 +394,7 @@ static int devfreq_notifier_call(struct notifier_block *nb, unsigned long type,
|
|||
* @devfreq: the devfreq struct
|
||||
* @skip: skip calling device_unregister().
|
||||
*/
|
||||
static void _remove_devfreq(struct devfreq *devfreq, bool skip)
|
||||
static void _remove_devfreq(struct devfreq *devfreq)
|
||||
{
|
||||
mutex_lock(&devfreq_list_lock);
|
||||
if (IS_ERR(find_device_devfreq(devfreq->dev.parent))) {
|
||||
|
@ -412,11 +412,6 @@ static void _remove_devfreq(struct devfreq *devfreq, bool skip)
|
|||
if (devfreq->profile->exit)
|
||||
devfreq->profile->exit(devfreq->dev.parent);
|
||||
|
||||
if (!skip && get_device(&devfreq->dev)) {
|
||||
device_unregister(&devfreq->dev);
|
||||
put_device(&devfreq->dev);
|
||||
}
|
||||
|
||||
mutex_destroy(&devfreq->lock);
|
||||
kfree(devfreq);
|
||||
}
|
||||
|
@ -426,14 +421,12 @@ static void _remove_devfreq(struct devfreq *devfreq, bool skip)
|
|||
* @dev: the devfreq device
|
||||
*
|
||||
* This calls _remove_devfreq() if _remove_devfreq() is not called.
|
||||
* Note that devfreq_dev_release() could be called by _remove_devfreq() as
|
||||
* well as by others unregistering the device.
|
||||
*/
|
||||
static void devfreq_dev_release(struct device *dev)
|
||||
{
|
||||
struct devfreq *devfreq = to_devfreq(dev);
|
||||
|
||||
_remove_devfreq(devfreq, true);
|
||||
_remove_devfreq(devfreq);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -544,12 +537,76 @@ int devfreq_remove_device(struct devfreq *devfreq)
|
|||
if (!devfreq)
|
||||
return -EINVAL;
|
||||
|
||||
_remove_devfreq(devfreq, false);
|
||||
device_unregister(&devfreq->dev);
|
||||
put_device(&devfreq->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(devfreq_remove_device);
|
||||
|
||||
static int devm_devfreq_dev_match(struct device *dev, void *res, void *data)
|
||||
{
|
||||
struct devfreq **r = res;
|
||||
|
||||
if (WARN_ON(!r || !*r))
|
||||
return 0;
|
||||
|
||||
return *r == data;
|
||||
}
|
||||
|
||||
static void devm_devfreq_dev_release(struct device *dev, void *res)
|
||||
{
|
||||
devfreq_remove_device(*(struct devfreq **)res);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_devfreq_add_device() - Resource-managed devfreq_add_device()
|
||||
* @dev: the device to add devfreq feature.
|
||||
* @profile: device-specific profile to run devfreq.
|
||||
* @governor_name: name of the policy to choose frequency.
|
||||
* @data: private data for the governor. The devfreq framework does not
|
||||
* touch this value.
|
||||
*
|
||||
* This function manages automatically the memory of devfreq device using device
|
||||
* resource management and simplify the free operation for memory of devfreq
|
||||
* device.
|
||||
*/
|
||||
struct devfreq *devm_devfreq_add_device(struct device *dev,
|
||||
struct devfreq_dev_profile *profile,
|
||||
const char *governor_name,
|
||||
void *data)
|
||||
{
|
||||
struct devfreq **ptr, *devfreq;
|
||||
|
||||
ptr = devres_alloc(devm_devfreq_dev_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
devfreq = devfreq_add_device(dev, profile, governor_name, data);
|
||||
if (IS_ERR(devfreq)) {
|
||||
devres_free(ptr);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
*ptr = devfreq;
|
||||
devres_add(dev, ptr);
|
||||
|
||||
return devfreq;
|
||||
}
|
||||
EXPORT_SYMBOL(devm_devfreq_add_device);
|
||||
|
||||
/**
|
||||
* devm_devfreq_remove_device() - Resource-managed devfreq_remove_device()
|
||||
* @dev: the device to add devfreq feature.
|
||||
* @devfreq: the devfreq instance to be removed
|
||||
*/
|
||||
void devm_devfreq_remove_device(struct device *dev, struct devfreq *devfreq)
|
||||
{
|
||||
WARN_ON(devres_release(dev, devm_devfreq_dev_release,
|
||||
devm_devfreq_dev_match, devfreq));
|
||||
}
|
||||
EXPORT_SYMBOL(devm_devfreq_remove_device);
|
||||
|
||||
/**
|
||||
* devfreq_suspend_device() - Suspend devfreq of a device.
|
||||
* @devfreq: the devfreq instance to be suspended
|
||||
|
@ -1112,6 +1169,54 @@ int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void devm_devfreq_opp_release(struct device *dev, void *res)
|
||||
{
|
||||
devfreq_unregister_opp_notifier(dev, *(struct devfreq **)res);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_ devfreq_register_opp_notifier()
|
||||
* - Resource-managed devfreq_register_opp_notifier()
|
||||
* @dev: The devfreq user device. (parent of devfreq)
|
||||
* @devfreq: The devfreq object.
|
||||
*/
|
||||
int devm_devfreq_register_opp_notifier(struct device *dev,
|
||||
struct devfreq *devfreq)
|
||||
{
|
||||
struct devfreq **ptr;
|
||||
int ret;
|
||||
|
||||
ptr = devres_alloc(devm_devfreq_opp_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devfreq_register_opp_notifier(dev, devfreq);
|
||||
if (ret) {
|
||||
devres_free(ptr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*ptr = devfreq;
|
||||
devres_add(dev, ptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(devm_devfreq_register_opp_notifier);
|
||||
|
||||
/**
|
||||
* devm_devfreq_unregister_opp_notifier()
|
||||
* - Resource-managed devfreq_unregister_opp_notifier()
|
||||
* @dev: The devfreq user device. (parent of devfreq)
|
||||
* @devfreq: The devfreq object.
|
||||
*/
|
||||
void devm_devfreq_unregister_opp_notifier(struct device *dev,
|
||||
struct devfreq *devfreq)
|
||||
{
|
||||
WARN_ON(devres_release(dev, devm_devfreq_opp_release,
|
||||
devm_devfreq_dev_match, devfreq));
|
||||
}
|
||||
EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier);
|
||||
|
||||
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
|
||||
MODULE_DESCRIPTION("devfreq class support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# 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
|
||||
|
|
|
@ -25,13 +25,9 @@
|
|||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
/* Exynos4 ASV has been in the mailing list, but not upstreamed, yet. */
|
||||
#ifdef CONFIG_EXYNOS_ASV
|
||||
extern unsigned int exynos_result_of_asv;
|
||||
#endif
|
||||
|
||||
#include <mach/map.h>
|
||||
|
||||
#include "exynos_ppmu.h"
|
||||
#include "exynos4_bus.h"
|
||||
|
||||
#define MAX_SAFEVOLT 1200000 /* 1.2V */
|
||||
|
@ -44,22 +40,6 @@ enum exynos4_busf_type {
|
|||
/* Assume that the bus is saturated if the utilization is 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 {
|
||||
LV_0 = 0,
|
||||
LV_1,
|
||||
|
@ -68,6 +48,13 @@ enum busclk_level_idx {
|
|||
LV_4,
|
||||
_LV_END
|
||||
};
|
||||
|
||||
enum exynos_ppmu_idx {
|
||||
PPMU_DMC0,
|
||||
PPMU_DMC1,
|
||||
PPMU_END,
|
||||
};
|
||||
|
||||
#define EX4210_LV_MAX LV_2
|
||||
#define EX4x12_LV_MAX LV_4
|
||||
#define EX4210_LV_NUM (LV_2 + 1)
|
||||
|
@ -91,7 +78,7 @@ struct busfreq_data {
|
|||
struct regulator *vdd_int;
|
||||
struct regulator *vdd_mif; /* Exynos4412/4212 only */
|
||||
struct busfreq_opp_info curr_oppinfo;
|
||||
struct exynos4_ppmu dmc[2];
|
||||
struct busfreq_ppmu_data ppmu_data;
|
||||
|
||||
struct notifier_block pm_notifier;
|
||||
struct mutex lock;
|
||||
|
@ -101,12 +88,6 @@ struct busfreq_data {
|
|||
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 */
|
||||
static struct bus_opp_table exynos4210_busclk_table[] = {
|
||||
{LV_0, 400000, 1150000},
|
||||
|
@ -524,57 +505,6 @@ static int exynos4x12_set_busclk(struct busfreq_data *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void busfreq_mon_reset(struct busfreq_data *data)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
void __iomem *ppmu_base = data->dmc[i].hw_base;
|
||||
|
||||
/* Reset PPMU */
|
||||
__raw_writel(0x8000000f, ppmu_base + 0xf010);
|
||||
__raw_writel(0x8000000f, ppmu_base + 0xf050);
|
||||
__raw_writel(0x6, ppmu_base + 0xf000);
|
||||
__raw_writel(0x0, ppmu_base + 0xf100);
|
||||
|
||||
/* Set PPMU Event */
|
||||
data->dmc[i].event = 0x6;
|
||||
__raw_writel(((data->dmc[i].event << 12) | 0x1),
|
||||
ppmu_base + 0xfc);
|
||||
|
||||
/* Start PPMU */
|
||||
__raw_writel(0x1, ppmu_base + 0xf000);
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos4_read_ppmu(struct busfreq_data *data)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
void __iomem *ppmu_base = data->dmc[i].hw_base;
|
||||
u32 overflow;
|
||||
|
||||
/* Stop PPMU */
|
||||
__raw_writel(0x0, ppmu_base + 0xf000);
|
||||
|
||||
/* Update local data from PPMU */
|
||||
overflow = __raw_readl(ppmu_base + 0xf050);
|
||||
|
||||
data->dmc[i].ccnt = __raw_readl(ppmu_base + 0xf100);
|
||||
data->dmc[i].ccnt_overflow = overflow & (1 << 31);
|
||||
|
||||
for (j = 0; j < PPMU_PMNCNT_MAX; j++) {
|
||||
data->dmc[i].count[j] = __raw_readl(
|
||||
ppmu_base + (0xf110 + (0x10 * j)));
|
||||
data->dmc[i].count_overflow[j] = overflow & (1 << j);
|
||||
}
|
||||
}
|
||||
|
||||
busfreq_mon_reset(data);
|
||||
}
|
||||
|
||||
static int exynos4x12_get_intspec(unsigned long mifclk)
|
||||
{
|
||||
int i = 0;
|
||||
|
@ -698,84 +628,35 @@ out:
|
|||
return err;
|
||||
}
|
||||
|
||||
static int exynos4_get_busier_dmc(struct busfreq_data *data)
|
||||
{
|
||||
u64 p0 = data->dmc[0].count[0];
|
||||
u64 p1 = data->dmc[1].count[0];
|
||||
|
||||
p0 *= data->dmc[1].ccnt;
|
||||
p1 *= data->dmc[0].ccnt;
|
||||
|
||||
if (data->dmc[1].ccnt == 0)
|
||||
return 0;
|
||||
|
||||
if (p0 > p1)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int exynos4_bus_get_dev_status(struct device *dev,
|
||||
struct devfreq_dev_status *stat)
|
||||
{
|
||||
struct busfreq_data *data = dev_get_drvdata(dev);
|
||||
int busier_dmc;
|
||||
int cycles_x2 = 2; /* 2 x cycles */
|
||||
void __iomem *addr;
|
||||
u32 timing;
|
||||
u32 memctrl;
|
||||
struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
|
||||
int busier;
|
||||
|
||||
exynos4_read_ppmu(data);
|
||||
busier_dmc = exynos4_get_busier_dmc(data);
|
||||
exynos_read_ppmu(ppmu_data);
|
||||
busier = exynos_get_busier_ppmu(ppmu_data);
|
||||
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 */
|
||||
stat->busy_time = data->dmc[busier_dmc].count[0] / 2 * (cycles_x2 + 2);
|
||||
stat->busy_time = ppmu_data->ppmu[busier].count[PPMU_PMNCNT3];
|
||||
stat->busy_time *= 100 / BUS_SATURATION_RATIO;
|
||||
stat->total_time = data->dmc[busier_dmc].ccnt;
|
||||
stat->total_time = ppmu_data->ppmu[busier].ccnt;
|
||||
|
||||
/* If the counters have overflown, retry */
|
||||
if (data->dmc[busier_dmc].ccnt_overflow ||
|
||||
data->dmc[busier_dmc].count_overflow[0])
|
||||
if (ppmu_data->ppmu[busier].ccnt_overflow ||
|
||||
ppmu_data->ppmu[busier].count_overflow[0])
|
||||
return -EAGAIN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exynos4_bus_exit(struct device *dev)
|
||||
{
|
||||
struct busfreq_data *data = dev_get_drvdata(dev);
|
||||
|
||||
devfreq_unregister_opp_notifier(dev, data->devfreq);
|
||||
}
|
||||
|
||||
static struct devfreq_dev_profile exynos4_devfreq_profile = {
|
||||
.initial_freq = 400000,
|
||||
.polling_ms = 50,
|
||||
.target = exynos4_bus_target,
|
||||
.get_dev_status = exynos4_bus_get_dev_status,
|
||||
.exit = exynos4_bus_exit,
|
||||
};
|
||||
|
||||
static int exynos4210_init_tables(struct busfreq_data *data)
|
||||
|
@ -837,11 +718,11 @@ static int exynos4210_init_tables(struct busfreq_data *data)
|
|||
data->top_divtable[i] = tmp;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXYNOS_ASV
|
||||
tmp = exynos4_result_of_asv;
|
||||
#else
|
||||
/*
|
||||
* TODO: init tmp based on busfreq_data
|
||||
* (device-tree or platform-data)
|
||||
*/
|
||||
tmp = 0; /* Max voltages for the reliability of the unknown */
|
||||
#endif
|
||||
|
||||
pr_debug("ASV Group of Exynos4 is %d\n", tmp);
|
||||
/* Use merged grouping for voltage */
|
||||
|
@ -922,11 +803,7 @@ static int exynos4x12_init_tables(struct busfreq_data *data)
|
|||
data->dmc_divtable[i] = tmp;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXYNOS_ASV
|
||||
tmp = exynos4_result_of_asv;
|
||||
#else
|
||||
tmp = 0; /* Max voltages for the reliability of the unknown */
|
||||
#endif
|
||||
|
||||
if (tmp > 8)
|
||||
tmp = 0;
|
||||
|
@ -1020,6 +897,7 @@ unlock:
|
|||
static int exynos4_busfreq_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct busfreq_data *data;
|
||||
struct busfreq_ppmu_data *ppmu_data;
|
||||
struct dev_pm_opp *opp;
|
||||
struct device *dev = &pdev->dev;
|
||||
int err = 0;
|
||||
|
@ -1030,9 +908,19 @@ static int exynos4_busfreq_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ppmu_data = &data->ppmu_data;
|
||||
ppmu_data->ppmu_end = PPMU_END;
|
||||
ppmu_data->ppmu = devm_kzalloc(dev,
|
||||
sizeof(struct exynos_ppmu) * PPMU_END,
|
||||
GFP_KERNEL);
|
||||
if (!ppmu_data->ppmu) {
|
||||
dev_err(dev, "Failed to allocate memory for exynos_ppmu\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
data->type = pdev->id_entry->driver_data;
|
||||
data->dmc[0].hw_base = S5P_VA_DMC0;
|
||||
data->dmc[1].hw_base = S5P_VA_DMC1;
|
||||
ppmu_data->ppmu[PPMU_DMC0].hw_base = S5P_VA_DMC0;
|
||||
ppmu_data->ppmu[PPMU_DMC1].hw_base = S5P_VA_DMC1;
|
||||
data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event;
|
||||
data->dev = dev;
|
||||
mutex_init(&data->lock);
|
||||
|
@ -1048,8 +936,11 @@ static int exynos4_busfreq_probe(struct platform_device *pdev)
|
|||
dev_err(dev, "Cannot determine the device id %d\n", data->type);
|
||||
err = -EINVAL;
|
||||
}
|
||||
if (err)
|
||||
if (err) {
|
||||
dev_err(dev, "Cannot initialize busfreq table %d\n",
|
||||
data->type);
|
||||
return err;
|
||||
}
|
||||
|
||||
data->vdd_int = devm_regulator_get(dev, "vdd_int");
|
||||
if (IS_ERR(data->vdd_int)) {
|
||||
|
@ -1079,19 +970,28 @@ static int exynos4_busfreq_probe(struct platform_device *pdev)
|
|||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
busfreq_mon_reset(data);
|
||||
|
||||
data->devfreq = devfreq_add_device(dev, &exynos4_devfreq_profile,
|
||||
data->devfreq = devm_devfreq_add_device(dev, &exynos4_devfreq_profile,
|
||||
"simple_ondemand", NULL);
|
||||
if (IS_ERR(data->devfreq))
|
||||
return PTR_ERR(data->devfreq);
|
||||
|
||||
devfreq_register_opp_notifier(dev, data->devfreq);
|
||||
/*
|
||||
* Start PPMU (Performance Profiling Monitoring Unit) to check
|
||||
* utilization of each IP in the Exynos4 SoC.
|
||||
*/
|
||||
busfreq_mon_reset(ppmu_data);
|
||||
|
||||
/* Register opp_notifier for Exynos4 busfreq */
|
||||
err = devm_devfreq_register_opp_notifier(dev, data->devfreq);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "Failed to register opp notifier\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Register pm_notifier for Exynos4 busfreq */
|
||||
err = register_pm_notifier(&data->pm_notifier);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to setup pm notifier\n");
|
||||
devfreq_remove_device(data->devfreq);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -1102,23 +1002,24 @@ static int exynos4_busfreq_remove(struct platform_device *pdev)
|
|||
{
|
||||
struct busfreq_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
/* Unregister all of notifier chain */
|
||||
unregister_pm_notifier(&data->pm_notifier);
|
||||
devfreq_remove_device(data->devfreq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int exynos4_busfreq_resume(struct device *dev)
|
||||
{
|
||||
struct busfreq_data *data = dev_get_drvdata(dev);
|
||||
struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
|
||||
|
||||
busfreq_mon_reset(data);
|
||||
busfreq_mon_reset(ppmu_data);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops exynos4_busfreq_pm = {
|
||||
.resume = exynos4_busfreq_resume,
|
||||
};
|
||||
static SIMPLE_DEV_PM_OPS(exynos4_busfreq_pm_ops, NULL, exynos4_busfreq_resume);
|
||||
|
||||
static const struct platform_device_id exynos4_busfreq_id[] = {
|
||||
{ "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 },
|
||||
|
@ -1134,7 +1035,7 @@ static struct platform_driver exynos4_busfreq_driver = {
|
|||
.driver = {
|
||||
.name = "exynos4-busfreq",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &exynos4_busfreq_pm,
|
||||
.pm = &exynos4_busfreq_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ struct busfreq_data_int {
|
|||
struct device *dev;
|
||||
struct devfreq *devfreq;
|
||||
struct regulator *vdd_int;
|
||||
struct exynos_ppmu ppmu[PPMU_END];
|
||||
struct busfreq_ppmu_data ppmu_data;
|
||||
unsigned long curr_freq;
|
||||
bool disabled;
|
||||
|
||||
|
@ -75,49 +75,6 @@ static struct int_bus_opp_table exynos5_int_opp_table[] = {
|
|||
{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)
|
||||
{
|
||||
|
@ -185,59 +142,33 @@ out:
|
|||
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);
|
||||
struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
|
||||
int busier_dmc;
|
||||
|
||||
exynos5_read_ppmu(data);
|
||||
busier_dmc = exynos5_get_busier_dmc(data);
|
||||
exynos_read_ppmu(ppmu_data);
|
||||
busier_dmc = exynos_get_busier_ppmu(ppmu_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 = ppmu_data->ppmu[busier_dmc].count[PPMU_PMNCNT3];
|
||||
stat->busy_time *= 100 / INT_BUS_SATURATION_RATIO;
|
||||
stat->total_time = data->ppmu[busier_dmc].ccnt;
|
||||
stat->total_time = ppmu_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)
|
||||
|
@ -315,6 +246,7 @@ unlock:
|
|||
static int exynos5_busfreq_int_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct busfreq_data_int *data;
|
||||
struct busfreq_ppmu_data *ppmu_data;
|
||||
struct dev_pm_opp *opp;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np;
|
||||
|
@ -330,16 +262,26 @@ static int exynos5_busfreq_int_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ppmu_data = &data->ppmu_data;
|
||||
ppmu_data->ppmu_end = PPMU_END;
|
||||
ppmu_data->ppmu = devm_kzalloc(dev,
|
||||
sizeof(struct exynos_ppmu) * PPMU_END,
|
||||
GFP_KERNEL);
|
||||
if (!ppmu_data->ppmu) {
|
||||
dev_err(dev, "Failed to allocate memory for exynos_ppmu\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++) {
|
||||
for (i = 0; i < ppmu_data->ppmu_end; i++) {
|
||||
/* map PPMU memory region */
|
||||
data->ppmu[i].hw_base = of_iomap(np, i);
|
||||
if (data->ppmu[i].hw_base == NULL) {
|
||||
ppmu_data->ppmu[i].hw_base = of_iomap(np, i);
|
||||
if (ppmu_data->ppmu[i].hw_base == NULL) {
|
||||
dev_err(&pdev->dev, "failed to map memory region\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
@ -390,32 +332,29 @@ static int exynos5_busfreq_int_probe(struct platform_device *pdev)
|
|||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
busfreq_mon_reset(data);
|
||||
busfreq_mon_reset(ppmu_data);
|
||||
|
||||
data->devfreq = devfreq_add_device(dev, &exynos5_devfreq_int_profile,
|
||||
data->devfreq = devm_devfreq_add_device(dev, &exynos5_devfreq_int_profile,
|
||||
"simple_ondemand", NULL);
|
||||
if (IS_ERR(data->devfreq))
|
||||
return PTR_ERR(data->devfreq);
|
||||
|
||||
if (IS_ERR(data->devfreq)) {
|
||||
err = PTR_ERR(data->devfreq);
|
||||
goto err_devfreq_add;
|
||||
err = devm_devfreq_register_opp_notifier(dev, data->devfreq);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "Failed to register opp notifier\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
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;
|
||||
return err;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_remove(struct platform_device *pdev)
|
||||
|
@ -424,24 +363,27 @@ static int exynos5_busfreq_int_remove(struct platform_device *pdev)
|
|||
|
||||
pm_qos_remove_request(&data->int_req);
|
||||
unregister_pm_notifier(&data->pm_notifier);
|
||||
devfreq_remove_device(data->devfreq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
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);
|
||||
struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
|
||||
|
||||
busfreq_mon_reset(data);
|
||||
busfreq_mon_reset(ppmu_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops exynos5_busfreq_int_pm = {
|
||||
.resume = exynos5_busfreq_int_resume,
|
||||
};
|
||||
#endif
|
||||
static SIMPLE_DEV_PM_OPS(exynos5_busfreq_int_pm_ops, NULL,
|
||||
exynos5_busfreq_int_resume);
|
||||
|
||||
/* platform device pointer for exynos5 devfreq device. */
|
||||
static struct platform_device *exynos5_devfreq_pdev;
|
||||
|
@ -452,7 +394,7 @@ static struct platform_driver exynos5_busfreq_int_driver = {
|
|||
.driver = {
|
||||
.name = "exynos5-bus-int",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &exynos5_busfreq_int_pm,
|
||||
.pm = &exynos5_busfreq_int_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -54,3 +54,63 @@ unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch)
|
|||
|
||||
return total;
|
||||
}
|
||||
|
||||
void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ppmu_data->ppmu_end; i++) {
|
||||
void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base;
|
||||
|
||||
/* Reset the performance and cycle counters */
|
||||
exynos_ppmu_reset(ppmu_base);
|
||||
|
||||
/* Setup count registers to monitor read/write transactions */
|
||||
ppmu_data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
|
||||
exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
|
||||
ppmu_data->ppmu[i].event[PPMU_PMNCNT3]);
|
||||
|
||||
exynos_ppmu_start(ppmu_base);
|
||||
}
|
||||
}
|
||||
|
||||
void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < ppmu_data->ppmu_end; i++) {
|
||||
void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base;
|
||||
|
||||
exynos_ppmu_stop(ppmu_base);
|
||||
|
||||
/* Update local data from PPMU */
|
||||
ppmu_data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
|
||||
|
||||
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
|
||||
if (ppmu_data->ppmu[i].event[j] == 0)
|
||||
ppmu_data->ppmu[i].count[j] = 0;
|
||||
else
|
||||
ppmu_data->ppmu[i].count[j] =
|
||||
exynos_ppmu_read(ppmu_base, j);
|
||||
}
|
||||
}
|
||||
|
||||
busfreq_mon_reset(ppmu_data);
|
||||
}
|
||||
|
||||
int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
int i, j, busy = 0;
|
||||
|
||||
for (i = 0; i < ppmu_data->ppmu_end; i++) {
|
||||
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
|
||||
if (ppmu_data->ppmu[i].count[j] > count) {
|
||||
count = ppmu_data->ppmu[i].count[j];
|
||||
busy = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return busy;
|
||||
}
|
||||
|
|
|
@ -69,10 +69,18 @@ struct exynos_ppmu {
|
|||
bool count_overflow[PPMU_PMNCNT_MAX];
|
||||
};
|
||||
|
||||
struct busfreq_ppmu_data {
|
||||
struct exynos_ppmu *ppmu;
|
||||
int ppmu_end;
|
||||
};
|
||||
|
||||
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);
|
||||
void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data);
|
||||
void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data);
|
||||
int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data);
|
||||
#endif /* __DEVFREQ_EXYNOS_PPMU_H */
|
||||
|
|
|
@ -181,6 +181,12 @@ extern struct devfreq *devfreq_add_device(struct device *dev,
|
|||
const char *governor_name,
|
||||
void *data);
|
||||
extern int devfreq_remove_device(struct devfreq *devfreq);
|
||||
extern struct devfreq *devm_devfreq_add_device(struct device *dev,
|
||||
struct devfreq_dev_profile *profile,
|
||||
const char *governor_name,
|
||||
void *data);
|
||||
extern void devm_devfreq_remove_device(struct device *dev,
|
||||
struct devfreq *devfreq);
|
||||
|
||||
/* Supposed to be called by PM_SLEEP/PM_RUNTIME callbacks */
|
||||
extern int devfreq_suspend_device(struct devfreq *devfreq);
|
||||
|
@ -193,6 +199,10 @@ extern int devfreq_register_opp_notifier(struct device *dev,
|
|||
struct devfreq *devfreq);
|
||||
extern int devfreq_unregister_opp_notifier(struct device *dev,
|
||||
struct devfreq *devfreq);
|
||||
extern int devm_devfreq_register_opp_notifier(struct device *dev,
|
||||
struct devfreq *devfreq);
|
||||
extern void devm_devfreq_unregister_opp_notifier(struct device *dev,
|
||||
struct devfreq *devfreq);
|
||||
|
||||
#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND)
|
||||
/**
|
||||
|
@ -220,7 +230,7 @@ static inline struct devfreq *devfreq_add_device(struct device *dev,
|
|||
const char *governor_name,
|
||||
void *data)
|
||||
{
|
||||
return NULL;
|
||||
return ERR_PTR(-ENOSYS);
|
||||
}
|
||||
|
||||
static inline int devfreq_remove_device(struct devfreq *devfreq)
|
||||
|
@ -228,6 +238,19 @@ static inline int devfreq_remove_device(struct devfreq *devfreq)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline struct devfreq *devm_devfreq_add_device(struct device *dev,
|
||||
struct devfreq_dev_profile *profile,
|
||||
const char *governor_name,
|
||||
void *data)
|
||||
{
|
||||
return ERR_PTR(-ENOSYS);
|
||||
}
|
||||
|
||||
static inline void devm_devfreq_remove_device(struct device *dev,
|
||||
struct devfreq *devfreq)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int devfreq_suspend_device(struct devfreq *devfreq)
|
||||
{
|
||||
return 0;
|
||||
|
@ -256,6 +279,16 @@ static inline int devfreq_unregister_opp_notifier(struct device *dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline int devm_devfreq_register_opp_notifier(struct device *dev,
|
||||
struct devfreq *devfreq)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline void devm_devfreq_unregister_opp_notifier(struct device *dev,
|
||||
struct devfreq *devfreq)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_PM_DEVFREQ */
|
||||
|
||||
#endif /* __LINUX_DEVFREQ_H__ */
|
||||
|
|
Загрузка…
Ссылка в новой задаче