arm: exynos: Add ppmu support for Exynos5
authorAbhilash Kesavan <a.kesavan@samsung.com>
Tue, 14 Aug 2012 12:00:13 +0000 (17:30 +0530)
committerGerrit <chrome-bot@google.com>
Tue, 28 Aug 2012 04:47:35 +0000 (21:47 -0700)
Platform Performance Monitoring Unit (PPMU) is used to monitor and
gather performance information for custom IPs such such as MFC. This
information is used by the busfreq driver to vary/control VDD_INT.

BUG=chrome-os-partner:11792
TEST=Build and boot Snow

Change-Id: I0c94bfb23c2715afa0d26ca8595b59c7780f3a45
Signed-off-by: Abhilash Kesavan <a.kesavan@samsung.com>
Reviewed-on: https://gerrit.chromium.org/gerrit/28942
Reviewed-by: Bryan Freed <bfreed@chromium.org>
Tested-by: Arjun.K.V <arjun.kv@samsung.com>
arch/arm/mach-exynos/Makefile
arch/arm/mach-exynos/include/mach/map.h
arch/arm/mach-exynos/include/mach/ppmu.h [new file with mode: 0644]
arch/arm/mach-exynos/ppmu.c [new file with mode: 0644]

index da643a8..5abea47 100644 (file)
@@ -14,7 +14,7 @@ obj-                          :=
 
 obj-$(CONFIG_ARCH_EXYNOS)      += common.o
 obj-$(CONFIG_ARCH_EXYNOS4)     += clock-exynos4.o
-obj-$(CONFIG_ARCH_EXYNOS5)     += clock-exynos5.o
+obj-$(CONFIG_ARCH_EXYNOS5)     += clock-exynos5.o ppmu.o
 obj-$(CONFIG_CPU_EXYNOS4210)   += clock-exynos4210.o
 obj-$(CONFIG_SOC_EXYNOS4212)   += clock-exynos4212.o
 
index 7116d0f..09d8c44 100644 (file)
 #define EXYNOS5_PA_SPI1                        0x12D30000
 #define EXYNOS5_PA_SPI2                        0x12D40000
 
+#define EXYNOS5_PA_PPMU_DDR_C          0x10C40000
+#define EXYNOS5_PA_PPMU_DDR_R1         0x10C50000
+#define EXYNOS5_PA_PPMU_CPU            0x10C60000
+#define EXYNOS5_PA_PPMU_DDR_L          0x10CB0000
+#define EXYNOS5_PA_PPMU_RIGHT0_BUS     0x13660000
+
 #define EXYNOS4_PA_GPIO1               0x11400000
 #define EXYNOS4_PA_GPIO2               0x11000000
 #define EXYNOS4_PA_GPIO3               0x03860000
diff --git a/arch/arm/mach-exynos/include/mach/ppmu.h b/arch/arm/mach-exynos/include/mach/ppmu.h
new file mode 100644 (file)
index 0000000..e224666
--- /dev/null
@@ -0,0 +1,89 @@
+/* linux/arch/arm/mach-exynos/include/mach/ppmu.h
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *             http://www.samsung.com/
+ *
+ * EXYNOS PPMU Header file
+ *
+ * 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.
+*/
+
+#ifndef __ASM_ARCH_PPMU_H
+#define __ASM_ARCH_PPMU_H __FILE__
+
+#define PPMU_NR_COUNTERS       4
+
+#define PPMU_CNTENS            0x10
+#define PPMU_CNTENC            0x20
+#define PPMU_INTENS            0x30
+#define PPMU_INTENC            0x40
+#define PPMU_FLAG              0x50
+
+#define PPMU_CCNT              0x100
+#define PPMU_PMCNT0            0x110
+#define PPMU_PMCNT_OFFSET      0x10
+
+#define PPMU_BEVT0SEL          0x1000
+#define PPMU_BEVTSEL_OFFSET    0x100
+#define PPMU_CNT_RESET         0x1800
+
+#define DEVT0_SEL              0x1000
+#define DEVT0_ID               0x1010
+#define DEVT0_IDMSK            0x1014
+#define DEVT_ID_OFFSET         0x100
+
+#define DEFAULT_WEIGHT         1
+#define MAX_CCNT               100
+#define RDWR_DATA_COUNT                0x7
+
+#define PMCNT_OFFSET(i)                (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * i))
+#define PPMU_BEVTSEL(i)                (PPMU_BEVT0SEL + (PPMU_BEVTSEL_OFFSET * i))
+
+enum ppmu_type {
+       PPMU_MIF,
+       PPMU_INT,
+       PPMU_TYPE_END,
+};
+
+enum exynos_ppmu {
+       PPMU_CPU,
+       PPMU_DDR_C,
+       PPMU_DDR_R1,
+       PPMU_DDR_L,
+       PPMU_RIGHT0_BUS,
+       PPMU_END,
+};
+
+extern unsigned long long ppmu_load[PPMU_END];
+
+struct exynos_ppmu_hw {
+       struct list_head node;
+       void __iomem *hw_base;
+       unsigned int base_addr;
+       unsigned int ccnt;
+       unsigned int event[PPMU_NR_COUNTERS];
+       unsigned int weight;
+       int usage;
+       int id;
+       struct device *dev;
+       unsigned int count[PPMU_NR_COUNTERS];
+};
+
+void exynos_ppmu_reset(struct exynos_ppmu_hw *ppmu);
+void exynos_ppmu_start(struct exynos_ppmu_hw *ppmu);
+void exynos_ppmu_stop(struct exynos_ppmu_hw *ppmu);
+void exynos_ppmu_setevent(struct exynos_ppmu_hw *ppmu,
+                                  unsigned int evt_num);
+unsigned long long exynos_ppmu_update(struct exynos_ppmu_hw *ppmu, int ch);
+
+void ppmu_init(struct exynos_ppmu_hw *ppmu, struct device *dev);
+void ppmu_start(struct device *dev);
+void ppmu_update(struct device *dev, int ch);
+void ppmu_reset(struct device *dev);
+
+extern struct exynos_ppmu_hw exynos_ppmu[];
+
+#endif /* __ASM_ARCH_PPMU_H */
+
diff --git a/arch/arm/mach-exynos/ppmu.c b/arch/arm/mach-exynos/ppmu.c
new file mode 100644 (file)
index 0000000..4f2f5c1
--- /dev/null
@@ -0,0 +1,164 @@
+/* linux/arch/arm/mach-exynos/ppmu.c
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *             http://www.samsung.com/
+ *
+ * EXYNOS5 - CPU 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/types.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/math64.h>
+
+#include <plat/cpu.h>
+
+#include <mach/map.h>
+#include <mach/regs-clock.h>
+#include <mach/ppmu.h>
+
+static LIST_HEAD(ppmu_list);
+
+unsigned long long ppmu_load[PPMU_END];
+
+void exynos_ppmu_reset(struct exynos_ppmu_hw *ppmu)
+{
+       writel(0x3 << 1, ppmu->hw_base);
+       writel(0x8000000f, ppmu->hw_base + PPMU_CNTENS);
+}
+
+void exynos_ppmu_setevent(struct exynos_ppmu_hw *ppmu,
+                               unsigned int evt_num)
+{
+       writel(ppmu->event[evt_num],
+                       ppmu->hw_base + PPMU_BEVTSEL(evt_num));
+}
+
+void exynos_ppmu_start(struct exynos_ppmu_hw *ppmu)
+{
+       writel(0x1, ppmu->hw_base);
+}
+
+void exynos_ppmu_stop(struct exynos_ppmu_hw *ppmu)
+{
+       writel(0x0, ppmu->hw_base);
+}
+
+unsigned long long exynos_ppmu_update(struct exynos_ppmu_hw *ppmu, int ch)
+{
+       unsigned long long total = 0;
+
+       ppmu->ccnt = readl(ppmu->hw_base + PPMU_CCNT);
+
+       if (ppmu->ccnt == 0)
+               ppmu->ccnt = MAX_CCNT;
+
+       if (ch >= PPMU_NR_COUNTERS || ppmu->event[ch] == 0)
+               return 0;
+
+       if (ch == 3)
+               total = (((u64)readl(ppmu->hw_base +
+               PMCNT_OFFSET(ch)) << 8) | readl(ppmu->hw_base +
+               PMCNT_OFFSET(ch + 1)));
+       else
+               total = readl(ppmu->hw_base + PMCNT_OFFSET(ch));
+
+       if (total > ppmu->ccnt)
+               total = ppmu->ccnt;
+
+       return div64_u64((total * ppmu->weight * 100), ppmu->ccnt);
+}
+
+void ppmu_start(struct device *dev)
+{
+       struct exynos_ppmu_hw *ppmu;
+
+       list_for_each_entry(ppmu, &ppmu_list, node)
+               if (ppmu->dev == dev)
+                       exynos_ppmu_start(ppmu);
+}
+
+void ppmu_update(struct device *dev, int ch)
+{
+       struct exynos_ppmu_hw *ppmu;
+
+       list_for_each_entry(ppmu, &ppmu_list, node)
+               if (ppmu->dev == dev) {
+                       exynos_ppmu_stop(ppmu);
+                       ppmu_load[ppmu->id] = exynos_ppmu_update(ppmu, ch);
+                       exynos_ppmu_reset(ppmu);
+               }
+}
+
+void ppmu_reset(struct device *dev)
+{
+       struct exynos_ppmu_hw *ppmu;
+       int i;
+
+       list_for_each_entry(ppmu, &ppmu_list, node) {
+               if (ppmu->dev == dev) {
+                       exynos_ppmu_stop(ppmu);
+                       for (i = 0; i < PPMU_NR_COUNTERS; i++)
+                               if (ppmu->event[i] != 0)
+                                       exynos_ppmu_setevent(ppmu, i);
+                       exynos_ppmu_reset(ppmu);
+               }
+       }
+}
+
+void ppmu_init(struct exynos_ppmu_hw *ppmu, struct device *dev)
+{
+       int i;
+
+       ppmu->hw_base = ioremap(ppmu->base_addr, SZ_8K);
+       if (ppmu->hw_base == NULL) {
+               printk(KERN_ERR "failed ioremap for ppmu\n");
+               return;
+       }
+
+       ppmu->dev = dev;
+       list_add(&ppmu->node, &ppmu_list);
+
+       for (i = 0; i < PPMU_NR_COUNTERS; i++)
+               if (ppmu->event[i] != 0)
+                       exynos_ppmu_setevent(ppmu, i);
+}
+
+struct exynos_ppmu_hw exynos_ppmu[] = {
+       [PPMU_CPU] = {
+               .id = PPMU_CPU,
+               .base_addr = EXYNOS5_PA_PPMU_CPU,
+               .event[3] = RDWR_DATA_COUNT,
+               .weight = DEFAULT_WEIGHT,
+       },
+       [PPMU_DDR_C] = {
+               .id = PPMU_DDR_C,
+               .base_addr = EXYNOS5_PA_PPMU_DDR_C,
+               .event[3] = RDWR_DATA_COUNT,
+               .weight = DEFAULT_WEIGHT,
+       },
+       [PPMU_DDR_R1] = {
+               .id = PPMU_DDR_R1,
+               .base_addr = EXYNOS5_PA_PPMU_DDR_R1,
+               .event[3] = RDWR_DATA_COUNT,
+               .weight = DEFAULT_WEIGHT,
+       },
+       [PPMU_DDR_L] = {
+               .id = PPMU_DDR_L,
+               .base_addr = EXYNOS5_PA_PPMU_DDR_L,
+               .event[3] = RDWR_DATA_COUNT,
+               .weight = DEFAULT_WEIGHT,
+       },
+       [PPMU_RIGHT0_BUS] = {
+               .id = PPMU_RIGHT0_BUS,
+               .base_addr = EXYNOS5_PA_PPMU_RIGHT0_BUS,
+               .event[3] = RDWR_DATA_COUNT,
+               .weight = DEFAULT_WEIGHT,
+       },
+};