diff options
Diffstat (limited to 'drivers/devfreq/exynos/exynos_ppmu.c')
-rw-r--r-- | drivers/devfreq/exynos/exynos_ppmu.c | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/drivers/devfreq/exynos/exynos_ppmu.c b/drivers/devfreq/exynos/exynos_ppmu.c new file mode 100644 index 000000000..97b75e513 --- /dev/null +++ b/drivers/devfreq/exynos/exynos_ppmu.c @@ -0,0 +1,119 @@ +/* + * 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; +} + +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); + } +} +EXPORT_SYMBOL(busfreq_mon_reset); + +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); +} +EXPORT_SYMBOL(exynos_read_ppmu); + +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; +} +EXPORT_SYMBOL(exynos_get_busier_ppmu); |