From 203fe6a999c494a18cfba432d6b43465f790fffb Mon Sep 17 00:00:00 2001 From: Jonghwa Lee Date: Thu, 15 May 2014 18:42:34 +0900 Subject: PM / devfreq : Make exynos4 busfreq driver to use PPMU not DMC internal one. This patch modifies exynos4 busfreq driver to use global PPMU not DMC block's internal PMU. The PPMU has different event set with DMC's and it'll be more adaquate to profile bus utilization. Change-Id: Ia5f6801cf6081edf5c76cad2af86978843bdf91a Signed-off-by: Jonghwa Lee --- arch/arm/boot/dts/exynos4x12.dtsi | 3 + drivers/devfreq/exynos/Makefile | 2 +- drivers/devfreq/exynos/exynos4_bus.c | 198 +++++++++++++++++------------------ drivers/devfreq/exynos/exynos_ppmu.h | 8 +- 4 files changed, 107 insertions(+), 104 deletions(-) diff --git a/arch/arm/boot/dts/exynos4x12.dtsi b/arch/arm/boot/dts/exynos4x12.dtsi index b3c105b1f74..474a9fca111 100644 --- a/arch/arm/boot/dts/exynos4x12.dtsi +++ b/arch/arm/boot/dts/exynos4x12.dtsi @@ -359,6 +359,9 @@ busfreq@0 { compatible = "samsung,exynos4x12-busfreq"; + reg = <0x106A0000 0x2000>, <0x106B0000 0x2000>, + clocks = <&clock 414>, <&clock 415>; + clock-names = "ppmudmc0", "ppmudmc1"; status = "disabled"; }; }; diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile index bfaaf5b0d61..49bc9175f92 100644 --- a/drivers/devfreq/exynos/Makefile +++ b/drivers/devfreq/exynos/Makefile @@ -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 diff --git a/drivers/devfreq/exynos/exynos4_bus.c b/drivers/devfreq/exynos/exynos4_bus.c index d3e9d9ab005..2e7bce22412 100644 --- a/drivers/devfreq/exynos/exynos4_bus.c +++ b/drivers/devfreq/exynos/exynos4_bus.c @@ -25,6 +25,9 @@ #include #include #include +#include + +#include "exynos_ppmu.h" /* Exynos4 ASV has been in the mailing list, but not upstreamed, yet. */ #ifdef CONFIG_EXYNOS_ASV @@ -33,8 +36,6 @@ extern unsigned int exynos_result_of_asv; #include -#include - #define MAX_SAFEVOLT 1200000 /* 1.2V */ enum exynos4_busf_type { @@ -45,22 +46,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, @@ -92,7 +77,8 @@ 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 exynos_ppmu ppmu[PPMU_CNT]; + struct clk *clk_ppmu[PPMU_CNT]; struct notifier_block pm_notifier; struct mutex lock; @@ -102,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}, @@ -528,24 +508,25 @@ static int exynos4x12_set_busclk(struct busfreq_data *data, static void busfreq_mon_reset(struct busfreq_data *data) { - unsigned int i; + int i, j; - for (i = 0; i < 2; i++) { - void __iomem *ppmu_base = data->dmc[i].hw_base; + for (i = 0; i < PPMU_CNT; i++) { + void __iomem *ppmu_base = data->ppmu[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); + exynos_ppmu_reset(ppmu_base); /* Set PPMU Event */ - data->dmc[i].event = 0x6; - __raw_writel(((data->dmc[i].event << 12) | 0x1), - ppmu_base + 0xfc); + for (j = 0; j < PPMU_PMNCNT_MAX; j++) { + if (data->ppmu[i].event[j] < 0) + continue; + + exynos_ppmu_setevent(ppmu_base, j, + data->ppmu[i].event[j]); + } /* Start PPMU */ - __raw_writel(0x1, ppmu_base + 0xf000); + exynos_ppmu_start(ppmu_base); } } @@ -553,23 +534,23 @@ 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; + for (i = 0; i < PPMU_CNT; i++) { + void __iomem *ppmu_base = data->ppmu[i].hw_base; u32 overflow; - /* Stop PPMU */ - __raw_writel(0x0, ppmu_base + 0xf000); + exynos_ppmu_stop(ppmu_base); /* Update local data from PPMU */ - overflow = __raw_readl(ppmu_base + 0xf050); + overflow = __raw_readl(ppmu_base + PPMU_FLAG); - data->dmc[i].ccnt = __raw_readl(ppmu_base + 0xf100); - data->dmc[i].ccnt_overflow = overflow & (1 << 31); + data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT); + data->ppmu[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); + if (data->ppmu[i].event[j] < 0) + data->ppmu[i].count[j] = 0; + data->ppmu[i].count[j] = exynos_ppmu_read(ppmu_base, j); + data->ppmu[i].count_overflow[j] = overflow & (1 << j); } } @@ -699,67 +680,31 @@ 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; + int i, j, _busy, _total; exynos4_read_ppmu(data); - busier_dmc = exynos4_get_busier_dmc(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; + for (i = 0, _busy = 0, _total = 0; i < PPMU_CNT; i++) { + for (j = 0; j < PPMU_PMNCNT_MAX; j++) { + if (data->ppmu[i].count[j] > _busy) { + /* If the counters have overflown, retry */ + if (data->ppmu[i].ccnt_overflow) + return -EAGAIN; + + _busy = data->ppmu[i].count[j]; + _total = data->ppmu[i].ccnt; + } + } } - /* Number of cycles spent on memory access */ - stat->busy_time = data->dmc[busier_dmc].count[0] / 2 * (cycles_x2 + 2); + stat->busy_time = _busy; stat->busy_time *= 100 / BUS_SATURATION_RATIO; - stat->total_time = data->dmc[busier_dmc].ccnt; - - /* If the counters have overflown, retry */ - if (data->dmc[busier_dmc].ccnt_overflow || - data->dmc[busier_dmc].count_overflow[0]) - return -EAGAIN; + stat->total_time = _total; + stat->current_frequency = data->curr_oppinfo.rate; return 0; } @@ -1039,9 +984,9 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) struct busfreq_data *data; struct opp *opp; struct device *dev = &pdev->dev; - int type, err = 0; + int i, type, err = 0; - data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data), GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(struct busfreq_data), GFP_KERNEL); if (data == NULL) { dev_err(dev, "Cannot allocate memory.\n"); return -ENOMEM; @@ -1054,10 +999,33 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) } else { type = pdev->id_entry->driver_data; } - data->type = type; - data->dmc[0].hw_base = S5P_VA_DMC0; - data->dmc[1].hw_base = S5P_VA_DMC1; + + /* Initialize PPMU data */ + for (i = 0; i < PPMU_CNT; i++) { + struct resource *res; + + /* Allocating iomap for PPMU SFRs */ + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) { + dev_err(dev, "No mem resource for ppmu[%d]\n", i); + return -ENOENT; + } + data->ppmu[i].hw_base = devm_ioremap_resource(dev, res); + if (IS_ERR(data->ppmu[i].hw_base)) + return PTR_ERR(data->ppmu[i].hw_base); + + /* + * TODO: + * PPMU's event would be set through board specific data. + * Now, only use 4th PPC to check read/write data count. + */ + data->ppmu[i].event[PPMU_PMNCNT0] = -1; + data->ppmu[i].event[PPMU_PMNCNT1] = -1; + data->ppmu[i].event[PPMU_PMNCNT2] = -1; + data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT; + } + data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event; data->dev = dev; @@ -1090,6 +1058,28 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) } } + data->clk_ppmu[PPMU_DMC0] = devm_clk_get(dev, "ppmudmc0"); + if (IS_ERR(data->clk_ppmu[PPMU_DMC0])) { + dev_err(dev, "Cannot get ppmudmc0 clock\n"); + return PTR_ERR(data->clk_ppmu[PPMU_DMC0]); + } + err = clk_prepare_enable(data->clk_ppmu[PPMU_DMC0]); + if (err) { + dev_err(dev, "Cannot enable ppmudmc0 clock\n"); + return err; + } + + data->clk_ppmu[PPMU_DMC1] = devm_clk_get(dev, "ppmudmc1"); + if (IS_ERR(data->clk_ppmu[PPMU_DMC1])) { + dev_err(dev, "Cannot get ppmudmc1 clock\n"); + return PTR_ERR(data->clk_ppmu[PPMU_DMC1]); + } + err = clk_prepare_enable(data->clk_ppmu[PPMU_DMC1]); + if (err) { + dev_err(dev, "Cannot enable ppmudmc0 clock\n"); + return err; + } + rcu_read_lock(); opp = opp_find_freq_floor(dev, &exynos4_devfreq_profile.initial_freq); if (IS_ERR(opp)) { @@ -1126,6 +1116,10 @@ static int exynos4_busfreq_probe(struct platform_device *pdev) static int exynos4_busfreq_remove(struct platform_device *pdev) { struct busfreq_data *data = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < PPMU_CNT; i++) + clk_disable_unprepare(data->clk_ppmu[i]); unregister_pm_notifier(&data->pm_notifier); devfreq_remove_device(data->devfreq); diff --git a/drivers/devfreq/exynos/exynos_ppmu.h b/drivers/devfreq/exynos/exynos_ppmu.h index 7dfb221eacc..16c81ad572f 100644 --- a/drivers/devfreq/exynos/exynos_ppmu.h +++ b/drivers/devfreq/exynos/exynos_ppmu.h @@ -46,12 +46,18 @@ enum ppmu_counter { PPMU_PMNCNT0, - PPMU_PMCCNT1, + PPMU_PMNCNT1, PPMU_PMNCNT2, PPMU_PMNCNT3, PPMU_PMNCNT_MAX, }; +enum ppmu_type { + PPMU_DMC0, + PPMU_DMC1, + PPMU_CNT, +}; + struct bus_opp_table { unsigned int idx; unsigned long clk; -- cgit v1.2.3