Commit 42a09284 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

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()
parents cd0c5bd3 6392bfd5
...@@ -70,19 +70,20 @@ config ARM_EXYNOS4_BUS_DEVFREQ ...@@ -70,19 +70,20 @@ config ARM_EXYNOS4_BUS_DEVFREQ
depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM
select ARCH_HAS_OPP select ARCH_HAS_OPP
select DEVFREQ_GOV_SIMPLE_ONDEMAND select DEVFREQ_GOV_SIMPLE_ONDEMAND
select PM_OPP
help help
This adds the DEVFREQ driver for Exynos4210 memory bus (vdd_int) This adds the DEVFREQ driver for Exynos4210 memory bus (vdd_int)
and Exynos4212/4412 memory interface and bus (vdd_mif + vdd_int). and Exynos4212/4412 memory interface and bus (vdd_mif + vdd_int).
It reads PPMU counters of memory controllers and adjusts It reads PPMU counters of memory controllers and adjusts
the operating frequencies and voltages with OPP support. the operating frequencies and voltages with OPP support.
To operate with optimal voltages, ASV support is required This does not yet operate with optimal voltages.
(CONFIG_EXYNOS_ASV).
config ARM_EXYNOS5_BUS_DEVFREQ config ARM_EXYNOS5_BUS_DEVFREQ
bool "ARM Exynos5250 Bus DEVFREQ Driver" bool "ARM Exynos5250 Bus DEVFREQ Driver"
depends on SOC_EXYNOS5250 depends on SOC_EXYNOS5250
select ARCH_HAS_OPP select ARCH_HAS_OPP
select DEVFREQ_GOV_SIMPLE_ONDEMAND select DEVFREQ_GOV_SIMPLE_ONDEMAND
select PM_OPP
help help
This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int). This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int).
It reads PPMU counters of memory controllers and adjusts the 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, ...@@ -394,7 +394,7 @@ static int devfreq_notifier_call(struct notifier_block *nb, unsigned long type,
* @devfreq: the devfreq struct * @devfreq: the devfreq struct
* @skip: skip calling device_unregister(). * @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); mutex_lock(&devfreq_list_lock);
if (IS_ERR(find_device_devfreq(devfreq->dev.parent))) { if (IS_ERR(find_device_devfreq(devfreq->dev.parent))) {
...@@ -412,11 +412,6 @@ static void _remove_devfreq(struct devfreq *devfreq, bool skip) ...@@ -412,11 +412,6 @@ static void _remove_devfreq(struct devfreq *devfreq, bool skip)
if (devfreq->profile->exit) if (devfreq->profile->exit)
devfreq->profile->exit(devfreq->dev.parent); devfreq->profile->exit(devfreq->dev.parent);
if (!skip && get_device(&devfreq->dev)) {
device_unregister(&devfreq->dev);
put_device(&devfreq->dev);
}
mutex_destroy(&devfreq->lock); mutex_destroy(&devfreq->lock);
kfree(devfreq); kfree(devfreq);
} }
...@@ -426,14 +421,12 @@ static void _remove_devfreq(struct devfreq *devfreq, bool skip) ...@@ -426,14 +421,12 @@ static void _remove_devfreq(struct devfreq *devfreq, bool skip)
* @dev: the devfreq device * @dev: the devfreq device
* *
* This calls _remove_devfreq() if _remove_devfreq() is not called. * 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) static void devfreq_dev_release(struct device *dev)
{ {
struct devfreq *devfreq = to_devfreq(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) ...@@ -544,12 +537,76 @@ int devfreq_remove_device(struct devfreq *devfreq)
if (!devfreq) if (!devfreq)
return -EINVAL; return -EINVAL;
_remove_devfreq(devfreq, false); device_unregister(&devfreq->dev);
put_device(&devfreq->dev);
return 0; return 0;
} }
EXPORT_SYMBOL(devfreq_remove_device); 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_suspend_device() - Suspend devfreq of a device.
* @devfreq: the devfreq instance to be suspended * @devfreq: the devfreq instance to be suspended
...@@ -1112,6 +1169,54 @@ int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq) ...@@ -1112,6 +1169,54 @@ int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq)
return ret; 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_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
MODULE_DESCRIPTION("devfreq class support"); MODULE_DESCRIPTION("devfreq class support");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
# 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,13 +25,9 @@ ...@@ -25,13 +25,9 @@
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/module.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 <mach/map.h>
#include "exynos_ppmu.h"
#include "exynos4_bus.h" #include "exynos4_bus.h"
#define MAX_SAFEVOLT 1200000 /* 1.2V */ #define MAX_SAFEVOLT 1200000 /* 1.2V */
...@@ -44,22 +40,6 @@ enum exynos4_busf_type { ...@@ -44,22 +40,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 +48,13 @@ enum busclk_level_idx { ...@@ -68,6 +48,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 +78,7 @@ struct busfreq_data { ...@@ -91,7 +78,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 busfreq_ppmu_data ppmu_data;
struct notifier_block pm_notifier; struct notifier_block pm_notifier;
struct mutex lock; struct mutex lock;
...@@ -101,12 +88,6 @@ struct busfreq_data { ...@@ -101,12 +88,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,57 +505,6 @@ static int exynos4x12_set_busclk(struct busfreq_data *data, ...@@ -524,57 +505,6 @@ static int exynos4x12_set_busclk(struct busfreq_data *data,
return 0; 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) static int exynos4x12_get_intspec(unsigned long mifclk)
{ {
int i = 0; int i = 0;
...@@ -698,84 +628,35 @@ static int exynos4_bus_target(struct device *dev, unsigned long *_freq, ...@@ -698,84 +628,35 @@ static int exynos4_bus_target(struct device *dev, unsigned long *_freq,
return err; 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, 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; struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
int cycles_x2 = 2; /* 2 x cycles */ int busier;
void __iomem *addr;
u32 timing;
u32 memctrl;
exynos4_read_ppmu(data);
busier_dmc = exynos4_get_busier_dmc(data);
stat->current_frequency = data->curr_oppinfo.rate;
if (busier_dmc) exynos_read_ppmu(ppmu_data);
addr = S5P_VA_DMC1; busier = exynos_get_busier_ppmu(ppmu_data);
else stat->current_frequency = data->curr_oppinfo.rate;
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 = ppmu_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 = ppmu_data->ppmu[busier].ccnt;
/* If the counters have overflown, retry */ /* If the counters have overflown, retry */
if (data->dmc[busier_dmc].ccnt_overflow || if (ppmu_data->ppmu[busier].ccnt_overflow ||
data->dmc[busier_dmc].count_overflow[0]) ppmu_data->ppmu[busier].count_overflow[0])
return -EAGAIN; return -EAGAIN;
return 0; 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 = { static struct devfreq_dev_profile exynos4_devfreq_profile = {
.initial_freq = 400000, .initial_freq = 400000,
.polling_ms = 50, .polling_ms = 50,
.target = exynos4_bus_target, .target = exynos4_bus_target,
.get_dev_status = exynos4_bus_get_dev_status, .get_dev_status = exynos4_bus_get_dev_status,
.exit = exynos4_bus_exit,
}; };
static int exynos4210_init_tables(struct busfreq_data *data) static int exynos4210_init_tables(struct busfreq_data *data)
...@@ -837,11 +718,11 @@ 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; data->top_divtable[i] = tmp;
} }
#ifdef CONFIG_EXYNOS_ASV /*
tmp = exynos4_result_of_asv; * TODO: init tmp based on busfreq_data
#else * (device-tree or platform-data)
*/
tmp = 0; /* Max voltages for the reliability of the unknown */ tmp = 0; /* Max voltages for the reliability of the unknown */
#endif
pr_debug("ASV Group of Exynos4 is %d\n", tmp); pr_debug("ASV Group of Exynos4 is %d\n", tmp);
/* Use merged grouping for voltage */ /* Use merged grouping for voltage */
...@@ -922,11 +803,7 @@ static int exynos4x12_init_tables(struct busfreq_data *data) ...@@ -922,11 +803,7 @@ static int exynos4x12_init_tables(struct busfreq_data *data)
data->dmc_divtable[i] = tmp; 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 */ tmp = 0; /* Max voltages for the reliability of the unknown */
#endif
if (tmp > 8) if (tmp > 8)
tmp = 0; tmp = 0;
...@@ -1020,6 +897,7 @@ static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this, ...@@ -1020,6 +897,7 @@ static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this,
static int exynos4_busfreq_probe(struct platform_device *pdev) static int exynos4_busfreq_probe(struct platform_device *pdev)
{ {
struct busfreq_data *data; struct busfreq_data *data;
struct busfreq_ppmu_data *ppmu_data;
struct dev_pm_opp *opp; struct dev_pm_opp *opp;
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
int err = 0; int err = 0;
...@@ -1030,9 +908,19 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) ...@@ -1030,9 +908,19 @@ static int exynos4_busfreq_probe(struct platform_device *pdev)
return -ENOMEM; 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->type = pdev->id_entry->driver_data;
data->dmc[0].hw_base = S5P_VA_DMC0; ppmu_data->ppmu[PPMU_DMC0].hw_base = S5P_VA_DMC0;
data->dmc[1].hw_base = S5P_VA_DMC1; ppmu_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);
...@@ -1048,8 +936,11 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) ...@@ -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); dev_err(dev, "Cannot determine the device id %d\n", data->type);
err = -EINVAL; err = -EINVAL;
} }
if (err) if (err) {
dev_err(dev, "Cannot initialize busfreq table %d\n",
data->type);
return err; return err;
}
data->vdd_int = devm_regulator_get(dev, "vdd_int"); data->vdd_int = devm_regulator_get(dev, "vdd_int");
if (IS_ERR(data->vdd_int)) { if (IS_ERR(data->vdd_int)) {
...@@ -1079,19 +970,28 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) ...@@ -1079,19 +970,28 @@ 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 = devm_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);
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); err = register_pm_notifier(&data->pm_notifier);
if (err) { if (err) {
dev_err(dev, "Failed to setup pm notifier\n"); dev_err(dev, "Failed to setup pm notifier\n");
devfreq_remove_device(data->devfreq);
return err; return err;
} }
...@@ -1102,23 +1002,24 @@ static int exynos4_busfreq_remove(struct platform_device *pdev) ...@@ -1102,23 +1002,24 @@ static int exynos4_busfreq_remove(struct platform_device *pdev)
{ {
struct busfreq_data *data = platform_get_drvdata(pdev); struct busfreq_data *data = platform_get_drvdata(pdev);
/* Unregister all of notifier chain */
unregister_pm_notifier(&data->pm_notifier); unregister_pm_notifier(&data->pm_notifier);
devfreq_remove_device(data->devfreq);
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP
static int exynos4_busfreq_resume(struct device *dev) static int exynos4_busfreq_resume(struct device *dev)
{ {
struct busfreq_data *data = dev_get_drvdata(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; return 0;
} }
#endif
static const struct dev_pm_ops exynos4_busfreq_pm = { static SIMPLE_DEV_PM_OPS(exynos4_busfreq_pm_ops, NULL, exynos4_busfreq_resume);
.resume = exynos4_busfreq_resume,
};
static const struct platform_device_id exynos4_busfreq_id[] = { static const struct platform_device_id exynos4_busfreq_id[] = {
{ "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 }, { "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 },
...@@ -1134,7 +1035,7 @@ static struct platform_driver exynos4_busfreq_driver = { ...@@ -1134,7 +1035,7 @@ static struct platform_driver exynos4_busfreq_driver = {
.driver = { .driver = {
.name = "exynos4-busfreq", .name = "exynos4-busfreq",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.pm = &exynos4_busfreq_pm, .pm = &exynos4_busfreq_pm_ops,
}, },
}; };
......
...@@ -50,7 +50,7 @@ struct busfreq_data_int { ...@@ -50,7 +50,7 @@ struct busfreq_data_int {
struct device *dev; struct device *dev;
struct devfreq *devfreq; struct devfreq *devfreq;
struct regulator *vdd_int; struct regulator *vdd_int;
struct exynos_ppmu ppmu[PPMU_END]; struct busfreq_ppmu_data ppmu_data;
unsigned long curr_freq; unsigned long curr_freq;
bool disabled; bool disabled;
...@@ -75,49 +75,6 @@ static struct int_bus_opp_table exynos5_int_opp_table[] = { ...@@ -75,49 +75,6 @@ static struct int_bus_opp_table exynos5_int_opp_table[] = {
{0, 0, 0}, {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, static int exynos5_int_setvolt(struct busfreq_data_int *data,
unsigned long volt) unsigned long volt)
{ {
...@@ -185,59 +142,33 @@ static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq, ...@@ -185,59 +142,33 @@ static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq,
return err; 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, static int exynos5_int_get_dev_status(struct device *dev,
struct devfreq_dev_status *stat) struct devfreq_dev_status *stat)
{ {
struct platform_device *pdev = container_of(dev, struct platform_device, struct platform_device *pdev = container_of(dev, struct platform_device,
dev); dev);
struct busfreq_data_int *data = platform_get_drvdata(pdev); struct busfreq_data_int *data = platform_get_drvdata(pdev);
struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
int busier_dmc; int busier_dmc;
exynos5_read_ppmu(data); exynos_read_ppmu(ppmu_data);
busier_dmc = exynos5_get_busier_dmc(data); busier_dmc = exynos_get_busier_ppmu(ppmu_data);
stat->current_frequency = data->curr_freq; stat->current_frequency = data->curr_freq;
/* Number of cycles spent on memory access */ /* 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->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; 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 = { static struct devfreq_dev_profile exynos5_devfreq_int_profile = {
.initial_freq = 160000, .initial_freq = 160000,
.polling_ms = 100, .polling_ms = 100,
.target = exynos5_busfreq_int_target, .target = exynos5_busfreq_int_target,
.get_dev_status = exynos5_int_get_dev_status, .get_dev_status = exynos5_int_get_dev_status,
.exit = exynos5_int_exit,
}; };
static int exynos5250_init_int_tables(struct busfreq_data_int *data) static int exynos5250_init_int_tables(struct busfreq_data_int *data)
...@@ -315,6 +246,7 @@ static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this, ...@@ -315,6 +246,7 @@ static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this,
static int exynos5_busfreq_int_probe(struct platform_device *pdev) static int exynos5_busfreq_int_probe(struct platform_device *pdev)
{ {
struct busfreq_data_int *data; struct busfreq_data_int *data;
struct busfreq_ppmu_data *ppmu_data;
struct dev_pm_opp *opp; struct dev_pm_opp *opp;
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct device_node *np; struct device_node *np;
...@@ -330,16 +262,26 @@ static int exynos5_busfreq_int_probe(struct platform_device *pdev) ...@@ -330,16 +262,26 @@ static int exynos5_busfreq_int_probe(struct platform_device *pdev)
return -ENOMEM; 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"); np = of_find_compatible_node(NULL, NULL, "samsung,exynos5250-ppmu");
if (np == NULL) { if (np == NULL) {
pr_err("Unable to find PPMU node\n"); pr_err("Unable to find PPMU node\n");
return -ENOENT; return -ENOENT;
} }
for (i = PPMU_RIGHT; i < PPMU_END; i++) { for (i = 0; i < ppmu_data->ppmu_end; i++) {
/* map PPMU memory region */ /* map PPMU memory region */
data->ppmu[i].hw_base = of_iomap(np, i); ppmu_data->ppmu[i].hw_base = of_iomap(np, i);
if (data->ppmu[i].hw_base == NULL) { if (ppmu_data->ppmu[i].hw_base == NULL) {
dev_err(&pdev->dev, "failed to map memory region\n"); dev_err(&pdev->dev, "failed to map memory region\n");
return -ENOMEM; return -ENOMEM;
} }
...@@ -390,32 +332,29 @@ static int exynos5_busfreq_int_probe(struct platform_device *pdev) ...@@ -390,32 +332,29 @@ static int exynos5_busfreq_int_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, data); 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); "simple_ondemand", NULL);
if (IS_ERR(data->devfreq))
return PTR_ERR(data->devfreq);
if (IS_ERR(data->devfreq)) { err = devm_devfreq_register_opp_notifier(dev, data->devfreq);
err = PTR_ERR(data->devfreq); if (err < 0) {
goto err_devfreq_add; 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); err = register_pm_notifier(&data->pm_notifier);
if (err) { if (err) {
dev_err(dev, "Failed to setup pm notifier\n"); 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 */ /* TODO: Add a new QOS class for int/mif bus */
pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1); pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1);
return 0; return 0;
err_devfreq_add:
devfreq_remove_device(data->devfreq);
return err;
} }
static int exynos5_busfreq_int_remove(struct platform_device *pdev) static int exynos5_busfreq_int_remove(struct platform_device *pdev)
...@@ -424,24 +363,27 @@ 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); pm_qos_remove_request(&data->int_req);
unregister_pm_notifier(&data->pm_notifier); unregister_pm_notifier(&data->pm_notifier);
devfreq_remove_device(data->devfreq);
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP
static int exynos5_busfreq_int_resume(struct device *dev) static int exynos5_busfreq_int_resume(struct device *dev)
{ {
struct platform_device *pdev = container_of(dev, struct platform_device, struct platform_device *pdev = container_of(dev, struct platform_device,
dev); dev);
struct busfreq_data_int *data = platform_get_drvdata(pdev); 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; return 0;
} }
static const struct dev_pm_ops exynos5_busfreq_int_pm = { static const struct dev_pm_ops exynos5_busfreq_int_pm = {
.resume = exynos5_busfreq_int_resume, .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. */ /* platform device pointer for exynos5 devfreq device. */
static struct platform_device *exynos5_devfreq_pdev; static struct platform_device *exynos5_devfreq_pdev;
...@@ -452,7 +394,7 @@ static struct platform_driver exynos5_busfreq_int_driver = { ...@@ -452,7 +394,7 @@ static struct platform_driver exynos5_busfreq_int_driver = {
.driver = { .driver = {
.name = "exynos5-bus-int", .name = "exynos5-bus-int",
.owner = THIS_MODULE, .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) ...@@ -54,3 +54,63 @@ unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch)
return total; 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 { ...@@ -69,10 +69,18 @@ struct exynos_ppmu {
bool count_overflow[PPMU_PMNCNT_MAX]; 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_reset(void __iomem *ppmu_base);
void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch, void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
unsigned int evt); unsigned int evt);
void exynos_ppmu_start(void __iomem *ppmu_base); void exynos_ppmu_start(void __iomem *ppmu_base);
void exynos_ppmu_stop(void __iomem *ppmu_base); void exynos_ppmu_stop(void __iomem *ppmu_base);
unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch); 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 */ #endif /* __DEVFREQ_EXYNOS_PPMU_H */
...@@ -181,6 +181,12 @@ extern struct devfreq *devfreq_add_device(struct device *dev, ...@@ -181,6 +181,12 @@ extern struct devfreq *devfreq_add_device(struct device *dev,
const char *governor_name, const char *governor_name,
void *data); void *data);
extern int devfreq_remove_device(struct devfreq *devfreq); 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 */ /* Supposed to be called by PM_SLEEP/PM_RUNTIME callbacks */
extern int devfreq_suspend_device(struct devfreq *devfreq); extern int devfreq_suspend_device(struct devfreq *devfreq);
...@@ -193,6 +199,10 @@ extern int devfreq_register_opp_notifier(struct device *dev, ...@@ -193,6 +199,10 @@ extern int devfreq_register_opp_notifier(struct device *dev,
struct devfreq *devfreq); struct devfreq *devfreq);
extern int devfreq_unregister_opp_notifier(struct device *dev, extern int devfreq_unregister_opp_notifier(struct device *dev,
struct devfreq *devfreq); 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) #if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND)
/** /**
...@@ -220,7 +230,7 @@ static inline struct devfreq *devfreq_add_device(struct device *dev, ...@@ -220,7 +230,7 @@ static inline struct devfreq *devfreq_add_device(struct device *dev,
const char *governor_name, const char *governor_name,
void *data) void *data)
{ {
return NULL; return ERR_PTR(-ENOSYS);
} }
static inline int devfreq_remove_device(struct devfreq *devfreq) static inline int devfreq_remove_device(struct devfreq *devfreq)
...@@ -228,6 +238,19 @@ static inline int devfreq_remove_device(struct devfreq *devfreq) ...@@ -228,6 +238,19 @@ static inline int devfreq_remove_device(struct devfreq *devfreq)
return 0; 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) static inline int devfreq_suspend_device(struct devfreq *devfreq)
{ {
return 0; return 0;
...@@ -256,6 +279,16 @@ static inline int devfreq_unregister_opp_notifier(struct device *dev, ...@@ -256,6 +279,16 @@ static inline int devfreq_unregister_opp_notifier(struct device *dev,
return -EINVAL; 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 /* CONFIG_PM_DEVFREQ */
#endif /* __LINUX_DEVFREQ_H__ */ #endif /* __LINUX_DEVFREQ_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment