From patchwork Tue Dec 9 14:13:03 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chanwoo Choi X-Patchwork-Id: 5463251 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 0B3199F30B for ; Tue, 9 Dec 2014 14:17:02 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id AACCB2011E for ; Tue, 9 Dec 2014 14:17:00 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id E087E20107 for ; Tue, 9 Dec 2014 14:16:58 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1XyLYG-0008Fq-5c; Tue, 09 Dec 2014 14:14:08 +0000 Received: from mailout2.samsung.com ([203.254.224.25]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1XyLXf-00080d-SF for linux-arm-kernel@lists.infradead.org; Tue, 09 Dec 2014 14:13:37 +0000 Received: from epcpsbgr4.samsung.com (u144.gpu120.samsung.co.kr [203.254.230.144]) by mailout2.samsung.com (Oracle Communications Messaging Server 7u4-24.01 (7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTP id <0NGB008TRJHW1M50@mailout2.samsung.com> for linux-arm-kernel@lists.infradead.org; Tue, 09 Dec 2014 23:13:08 +0900 (KST) Received: from epcpsbgm1.samsung.com ( [172.20.52.116]) by epcpsbgr4.samsung.com (EPCPMTA) with SMTP id D7.EB.18167.47307845; Tue, 09 Dec 2014 23:13:08 +0900 (KST) X-AuditID: cbfee690-f79ab6d0000046f7-76-548703746e0f Received: from epmmp1.local.host ( [203.254.227.16]) by epcpsbgm1.samsung.com (EPCPMTA) with SMTP id 92.06.20081.47307845; Tue, 09 Dec 2014 23:13:08 +0900 (KST) Received: from chan.10.32.193.11 ([10.252.81.195]) by mmp1.samsung.com (Oracle Communications Messaging Server 7u4-24.01 (7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTPA id <0NGB00IM3JHVFV90@mmp1.samsung.com>; Tue, 09 Dec 2014 23:13:08 +0900 (KST) From: Chanwoo Choi To: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCHv2 4/7] devfreq: event: Add exynos-ppmu devfreq event driver Date: Tue, 09 Dec 2014 23:13:03 +0900 Message-id: <1418134386-14681-5-git-send-email-cw00.choi@samsung.com> X-Mailer: git-send-email 1.8.5.5 In-reply-to: <1418134386-14681-1-git-send-email-cw00.choi@samsung.com> References: <1418134386-14681-1-git-send-email-cw00.choi@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFvrLLMWRmVeSWpSXmKPExsWyRsSkRLeEuT3EYOl1GYvHaxYzWWycsZ7V 4vqX56wW84+cY7V4/cLQonfBVTaLs01v2C02Pb7GanF51xw2i8+9RxgtZpzfx2Rxu3EFm8Xj FW/ZLVbt+sPowOexc9Zddo/Fe14yeWxeUu/Rt2UVo8fnTXIBrFFcNimpOZllqUX6dglcGW96 HrEVLMqomLXSs4FxZ1gXIyeHhICJxIWFS9kgbDGJC/fWA9lcHEICSxklll98xAxX1L2AHSKx iFFi+p+3UE4Tk8SbXbtZQarYBLQk9r+4ATZKRMBK4vT/DmaQImaB80wSSxbuBxslLOAv8Wj2 QSYQm0VAVeLXz1NgDbwCrhIzr2+HukNBYtnymWBDOQXcJJp2N7KA2EJANR9OtIJtlhA4xS7R uf48C8QgAYlvkw8B2RxACVmJTQegzpaUOLjiBssERuEFjAyrGEVTC5ILipPSi0z0ihNzi0vz 0vWS83M3MQJj5fS/ZxN2MN47YH2IUYCDUYmHV8OyLUSINbGsuDL3EKMp0IaJzFKiyfnAiMwr iTc0NjOyMDUxNTYytzRTEud9LfUzWEggPbEkNTs1tSC1KL6oNCe1+BAjEwenVAOjDGdoWd4S zUeMd+SsTs8I6crpNzH1yElyschhypj9zbqu86PVkb7VxWornyUrZX77vs/wZlrYw5ALnkc9 tCSmKLEX81VUiBY7fW1d5H2kc2uGWCTDmanfLjrl9HNOedb9gm31gysnc2X+vnnVHqbhoCsw f8emdWtPfo9frCF4d1b4WWkpszYlluKMREMt5qLiRABYK8zAkAIAAA== X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFjrOIsWRmVeSWpSXmKPExsVy+t9jAd0S5vYQgwMzBC0er1nMZLFxxnpW i+tfnrNazD9yjtXi9QtDi94FV9kszja9YbfY9Pgaq8XlXXPYLD73HmG0mHF+H5PF7cYVbBaP V7xlt1i16w+jA5/Hzll32T0W73nJ5LF5Sb1H35ZVjB6fN8kFsEY1MNpkpCampBYppOYl56dk 5qXbKnkHxzvHm5oZGOoaWlqYKynkJeam2iq5+AToumXmAB2qpFCWmFMKFApILC5W0rfDNCE0 xE3XAqYxQtc3JAiux8gADSSsYcx40/OIrWBRRsWslZ4NjDvDuhg5OSQETCQudC9gh7DFJC7c W8/WxcjFISSwiFFi+p+37BBOE5PEm127WUGq2AS0JPa/uMEGYosIWEmc/t/BDFLELHCeSWLJ wv3MIAlhAX+JR7MPMoHYLAKqEr9+ngJr4BVwlZh5fTsbxDoFiWXLZ4IN5RRwk2ja3cgCYgsB 1Xw40co+gZF3ASPDKkbR1ILkguKk9FxDveLE3OLSvHS95PzcTYzgSHwmtYNxZYPFIUYBDkYl Hl5Ny7YQIdbEsuLK3EOMEhzMSiK8U44ChXhTEiurUovy44tKc1KLDzGaAl01kVlKNDkfmCTy SuINjU3MjCyNzA0tjIzNlcR5leyBmgTSE0tSs1NTC1KLYPqYODilGhjzf7C1qD66bhTRdtbg +eVw76cpe2cXqCx+eeqIp6HvHibrrhTJF4EbGJbohXGGOev3HJ4eF1FUdunlZmaZBSoxd54L rFq55rHCx0sCLBvXfbpXx5Uq/3EVx0exKzeFzkbWvl/6XjjqZ+A36ewbRp8yuy89Z2SsNn+7 9et05U3Nzwr3xkv/DZNWYinOSDTUYi4qTgQAMeoqkNoCAAA= DLP-Filter: Pass X-MTR: 20000000000000000@CPGS X-CFilter-Loop: Reflected X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20141209_061332_291090_DCFC32B2 X-CRM114-Status: GOOD ( 23.25 ) X-Spam-Score: -5.0 (-----) Cc: devicetree@vger.kernel.org, k.kozlowski@samsung.com, kgene.kim@samsung.com, b.zolnierkie@samsung.com, rafael.j.wysocki@intel.com, tomasz.figa@gmail.com, cw00.choi@samsung.com, kyungmin.park@samsung.com, myungjoo.ham@samsung.com, linux-samsung-soc@vger.kernel.org, a.kesavan@samsung.com, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_LOW, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This patch add exynos-ppmu devfreq event driver to provider raw data about the utilization of each IP in Exynos SoC series. Cc: MyungJoo Ham Cc: Kyungmin Park Signed-off-by: Chanwoo Choi --- drivers/devfreq/Kconfig | 9 + drivers/devfreq/event/Makefile | 1 + drivers/devfreq/event/exynos-ppmu.c | 409 ++++++++++++++++++++++++++++++++++++ 3 files changed, 419 insertions(+) create mode 100644 drivers/devfreq/event/exynos-ppmu.c diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 4d15b62..d4559f7 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -89,4 +89,13 @@ config ARM_EXYNOS5_BUS_DEVFREQ comment "DEVFREQ Event Drivers" +config DEVFREQ_EVENT_EXYNOS_PPMU + bool "EXYNOS PPMU (Performance Profiling Monitoring Unit) DEVFREQ event Driver" + depends on ARCH_EXYNOS + select PM_OPP + help + This add the DEVFREQ event driver for Exynos SoC. It provides PPMU + (Performance Profiling Monitoring Unit) counters to estimate the + utilization of each module. + endif # PM_DEVFREQ diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile index dc56005..be146ea 100644 --- a/drivers/devfreq/event/Makefile +++ b/drivers/devfreq/event/Makefile @@ -1 +1,2 @@ # Exynos DEVFREQ Event Drivers +obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o diff --git a/drivers/devfreq/event/exynos-ppmu.c b/drivers/devfreq/event/exynos-ppmu.c new file mode 100644 index 0000000..2706f23 --- /dev/null +++ b/drivers/devfreq/event/exynos-ppmu.c @@ -0,0 +1,409 @@ +/* + * exynos_ppmu.c - EXYNOS PPMU (Performance Profiling Monitoring Units) support + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi + * + * 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. + * + * This driver is based on drivers/devfreq/exynos/exynos_ppmu.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PPMU_ENABLE BIT(0) +#define PPMU_DISABLE 0x0 +#define PPMU_CYCLE_RESET BIT(1) +#define PPMU_COUNTER_RESET BIT(2) + +#define PPMU_ENABLE_COUNT0 BIT(0) +#define PPMU_ENABLE_COUNT1 BIT(1) +#define PPMU_ENABLE_COUNT2 BIT(2) +#define PPMU_ENABLE_COUNT3 BIT(3) +#define PPMU_ENABLE_CYCLE BIT(31) + +#define PPMU_CNTENS 0x10 +#define PPMU_FLAG 0x50 +#define PPMU_CCNT_OVERFLOW BIT(31) +#define PPMU_CCNT 0x100 + +#define PPMU_PMCNT0 0x110 +#define PPMU_PMCNT_OFFSET 0x10 +#define PMCNT_OFFSET(x) (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * x)) + +#define PPMU_BEVT0SEL 0x1000 +#define PPMU_BEVTSEL_OFFSET 0x100 +#define PPMU_BEVTSEL(x) (PPMU_BEVT0SEL + (x * PPMU_BEVTSEL_OFFSET)) + +#define RD_DATA_COUNT 0x5 +#define WR_DATA_COUNT 0x6 +#define RDWR_DATA_COUNT 0x7 + +enum ppmu_counter { + PPMU_PMNCNT0, + PPMU_PMNCNT1, + PPMU_PMNCNT2, + PPMU_PMNCNT3, + PPMU_PMNCNT_MAX, +}; + +/* Platform data */ +struct exynos_ppmu_data { + struct devfreq *devfreq; + struct devfreq_event_dev **event_dev; + struct devfreq_event_desc *desc; + unsigned int num_events; + + struct device *dev; + struct clk *clk_ppmu; + struct mutex lock; + + struct __exynos_ppmu { + void __iomem *hw_base; + unsigned int ccnt; + unsigned int event[PPMU_PMNCNT_MAX]; + unsigned int count[PPMU_PMNCNT_MAX]; + unsigned long long ns; + ktime_t reset_time; + bool ccnt_overflow; + bool count_overflow[PPMU_PMNCNT_MAX]; + } ppmu; +}; + +struct __exynos_ppmu_events { + char *name; + int id; +} ppmu_events[] = { + { "ppmu-dmc0-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-dmc0-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-dmc0-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-dmc0-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-dmc1-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-dmc1-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-dmc1-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-dmc1-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-cpu-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-cpu-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-cpu-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-cpu-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-rightbus-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-rightbus-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-rightbus-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-rightbus-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-leftbus-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-leftbus-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-leftbus-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-leftbus-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-camif-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-camif-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-camif-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-camif-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-lcd0-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-lcd0-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-lcd0-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-lcd0-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-g3d-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-g3d-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-g3d-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-g3d-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-mfc-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-mfc-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-mfc-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-mfc-pmcnt3", PPMU_PMNCNT3 }, + + { "ppmu-fsys-pmcnt0", PPMU_PMNCNT0 }, + { "ppmu-fsys-pmcnt1", PPMU_PMNCNT1 }, + { "ppmu-fsys-pmcnt2", PPMU_PMNCNT2 }, + { "ppmu-fsys-pmcnt3", PPMU_PMNCNT3 }, + { /* sentinel */ }, +}; + +static int exynos_ppmu_enable(struct devfreq_event_dev *event_dev) +{ + struct exynos_ppmu_data *exynos_ppmu = event_dev_get_drvdata(event_dev); + + __raw_writel(PPMU_ENABLE, exynos_ppmu->ppmu.hw_base); + + return 0; +} + +static int exynos_ppmu_disable(struct devfreq_event_dev *event_dev) +{ + struct exynos_ppmu_data *exynos_ppmu = event_dev_get_drvdata(event_dev); + + __raw_writel(PPMU_DISABLE, exynos_ppmu->ppmu.hw_base); + + return 0; +} + +static bool exynos_ppmu_is_enabled(struct devfreq_event_dev *event_dev) +{ + struct exynos_ppmu_data *exynos_ppmu = event_dev_get_drvdata(event_dev); + int val; + + val = __raw_readl(exynos_ppmu->ppmu.hw_base); + if (val & PPMU_ENABLE) + return true; + + return false; +} + +static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *event_dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) + if (!strcmp(event_dev->desc->name, ppmu_events[i].name)) + return ppmu_events[i].id; + + return -EINVAL; +} + +static int exynos_ppmu_set_event(struct devfreq_event_dev *event_dev, + enum devfreq_event_type event_type) +{ + struct exynos_ppmu_data *exynos_ppmu = event_dev_get_drvdata(event_dev); + void __iomem *ppmu_base = exynos_ppmu->ppmu.hw_base; + int id = exynos_ppmu_find_ppmu_id(event_dev); + + __raw_writel(RDWR_DATA_COUNT, ppmu_base + PPMU_BEVTSEL(id)); + + return 0; +} + +static int exynos_ppmu_get_event(struct devfreq_event_dev *event_dev, + enum devfreq_event_type event_type, + int *total_event) +{ + struct exynos_ppmu_data *exynos_ppmu = event_dev_get_drvdata(event_dev); + void __iomem *ppmu_base = exynos_ppmu->ppmu.hw_base; + int id = exynos_ppmu_find_ppmu_id(event_dev); + int cnt, total_cnt; + + total_cnt = __raw_readl(ppmu_base + PPMU_CCNT); + + if (id == PPMU_PMNCNT3) + cnt = ((__raw_readl(ppmu_base + PMCNT_OFFSET(id)) << 8) | + __raw_readl(ppmu_base + PMCNT_OFFSET(id + 1))); + else + cnt = __raw_readl(ppmu_base + PMCNT_OFFSET(id)); + + *total_event = total_cnt; + + return cnt; +} + +static int exynos_ppmu_reset(struct devfreq_event_dev *event_dev) +{ + struct exynos_ppmu_data *exynos_ppmu = event_dev_get_drvdata(event_dev); + void __iomem *ppmu_base = exynos_ppmu->ppmu.hw_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); + __raw_writel(PPMU_ENABLE, exynos_ppmu->ppmu.hw_base); + + return 0; +} + +static struct devfreq_event_ops exynos_ppmu_ops = { + .enable = exynos_ppmu_enable, + .disable = exynos_ppmu_disable, + .is_enabled = exynos_ppmu_is_enabled, + .set_event = exynos_ppmu_set_event, + .get_event = exynos_ppmu_get_event, + .reset = exynos_ppmu_reset, +}; + +static int of_get_devfreq_events(struct device_node *np, + struct exynos_ppmu_data *exynos_ppmu) +{ + struct devfreq_event_desc *desc; + struct device *dev = exynos_ppmu->dev; + struct device_node *events_np, *node; + int i, j, count; + + events_np = of_find_node_by_name(np, "events"); + if (!events_np) { + dev_err(dev, "Failed to find ppmus sub-node\n"); + return -EINVAL; + } + + count = of_get_child_count(events_np); + desc = devm_kzalloc(dev, sizeof(struct devfreq_event_desc) * count, + GFP_KERNEL); + if (!desc) + return -ENOMEM; + exynos_ppmu->num_events = count; + + j = 0; + for_each_child_of_node(events_np, node) { + for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) { + if (!of_node_cmp(node->name, ppmu_events[i].name)) + break; + } + + if (i == ARRAY_SIZE(ppmu_events)) { + dev_warn(dev, + "don't know how to configure events : %s\n", + node->name); + continue; + } + desc[j].ops = &exynos_ppmu_ops; + desc[j].driver_data = exynos_ppmu; + desc[j].event_type |= DEVFREQ_EVENT_TYPE_RAW_DATA; + of_property_read_string(node, "event-name", &desc[j].name); + j++; + } + exynos_ppmu->desc = desc; + + return 0; +} + +static int exynos_ppmu_parse_dt(struct exynos_ppmu_data *exynos_ppmu) +{ + struct device *dev = exynos_ppmu->dev; + struct device_node *np = dev->of_node; + int ret = 0; + + if (!np) { + dev_err(dev, "Failed to find devicetree node\n"); + return -EINVAL; + } + + /* Maps the memory mapped IO to control PPMU register */ + exynos_ppmu->ppmu.hw_base = of_iomap(np, 0); + if (IS_ERR_OR_NULL(exynos_ppmu->ppmu.hw_base)) { + dev_err(dev, "Failed to map memory region\n"); + ret = -EINVAL; + goto err_iomap; + } + + /* FIXME: Get the clock of ppmu and enable this clock */ + exynos_ppmu->clk_ppmu = devm_clk_get(dev, "ppmu"); + if (IS_ERR(exynos_ppmu->clk_ppmu)) + dev_warn(dev, "Failed to get PPMU clock\n"); + + ret = of_get_devfreq_events(np, exynos_ppmu); + if (ret < 0) { + dev_err(dev, "Failed to parse exynos ppmu dt node\n"); + goto err_clock; + } + + return 0; + +err_clock: + clk_disable_unprepare(exynos_ppmu->clk_ppmu); +err_iomap: + iounmap(exynos_ppmu->ppmu.hw_base); + + return ret; +} + +static int exynos_ppmu_probe(struct platform_device *pdev) +{ + struct exynos_ppmu_data *exynos_ppmu; + struct devfreq_event_dev **event_dev; + struct devfreq_event_desc *desc; + int i, ret = 0, size; + + /* Allocate the memory of exynos_ppmu_data and initialize it */ + exynos_ppmu = devm_kzalloc(&pdev->dev, sizeof(struct exynos_ppmu_data), + GFP_KERNEL); + if (!exynos_ppmu) + return -ENOMEM; + + mutex_init(&exynos_ppmu->lock); + exynos_ppmu->dev = &pdev->dev; + + /* Parse dt data to get resource */ + ret = exynos_ppmu_parse_dt(exynos_ppmu); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to parse DT node for resource\n"); + return ret; + } + desc = exynos_ppmu->desc; + + size = sizeof(struct devfreq_event_dev *) * exynos_ppmu->num_events; + exynos_ppmu->event_dev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!exynos_ppmu->event_dev) { + dev_err(&pdev->dev, + "Failed to allocate memory devfreq_event_dev list\n"); + return -ENOMEM; + } + event_dev = exynos_ppmu->event_dev; + platform_set_drvdata(pdev, exynos_ppmu); + + for (i = 0; i < exynos_ppmu->num_events; i++) { + event_dev[i] = devfreq_add_event_device(&pdev->dev, &desc[i]); + if (IS_ERR(event_dev)) { + ret = PTR_ERR(event_dev); + dev_err(&pdev->dev, "Failed to add devfreq evt dev\n"); + goto err; + } + } + + return 0; +err: + clk_disable_unprepare(exynos_ppmu->clk_ppmu); + iounmap(exynos_ppmu->ppmu.hw_base); + + return ret; +} + +static int exynos_ppmu_remove(struct platform_device *pdev) +{ + struct exynos_ppmu_data *exynos_ppmu = platform_get_drvdata(pdev); + + clk_disable_unprepare(exynos_ppmu->clk_ppmu); + iounmap(exynos_ppmu->ppmu.hw_base); + + /* Remove devfreq instance */ + devfreq_remove_device(exynos_ppmu->devfreq); + + return 0; +} + +static struct of_device_id exynos_ppmu_id_match[] = { + { .compatible = "samsung,exynos-ppmu", }, + { /* sentinel */ }, +}; + +static struct platform_driver exynos_ppmu_driver = { + .probe = exynos_ppmu_probe, + .remove = exynos_ppmu_remove, + .driver = { + .name = "exynos-ppmu", + .owner = THIS_MODULE, + .of_match_table = exynos_ppmu_id_match, + }, +}; + +module_platform_driver(exynos_ppmu_driver); + +MODULE_DESCRIPTION("EXYNOS PPMU(Performance Profiling Monitoring Unit) driver"); +MODULE_AUTHOR("Chanwoo Choi "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:exynos-ppmu");