From patchwork Fri Apr 8 04:24:50 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chanwoo Choi X-Patchwork-Id: 8780031 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.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 6A3ED9F659 for ; Fri, 8 Apr 2016 04:28:00 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id E22CD20274 for ; Fri, 8 Apr 2016 04:27:58 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 6E2D02026F for ; Fri, 8 Apr 2016 04:27:57 +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 1aoNzd-0007I3-HL; Fri, 08 Apr 2016 04:26:01 +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 1aoNzD-00079x-3F for linux-arm-kernel@lists.infradead.org; Fri, 08 Apr 2016 04:25:38 +0000 Received: from epcpsbgr2.samsung.com (u142.gpu120.samsung.co.kr [203.254.230.142]) by mailout2.samsung.com (Oracle Communications Messaging Server 7.0.5.31.0 64bit (built May 5 2014)) with ESMTP id <0O5A00JUTS9ZT300@mailout2.samsung.com> for linux-arm-kernel@lists.infradead.org; Fri, 08 Apr 2016 13:25:11 +0900 (KST) Received: from epcpsbgm2new.samsung.com ( [172.20.52.115]) by epcpsbgr2.samsung.com (EPCPMTA) with SMTP id 7E.62.04785.7A237075; Fri, 8 Apr 2016 13:25:11 +0900 (KST) X-AuditID: cbfee68e-f79d96d0000012b1-bf-570732a7c9d1 Received: from epmmp2 ( [203.254.227.17]) by epcpsbgm2new.samsung.com (EPCPMTA) with SMTP id 00.D6.06699.7A237075; Fri, 8 Apr 2016 13:25:11 +0900 (KST) Received: from chan.10.32.193.11 ([10.113.62.212]) by mmp2.samsung.com (Oracle Communications Messaging Server 7.0.5.31.0 64bit (built May 5 2014)) with ESMTPA id <0O5A00DKDS9YHX20@mmp2.samsung.com>; Fri, 08 Apr 2016 13:25:11 +0900 (KST) From: Chanwoo Choi To: myungjoo.ham@samsung.com, kyungmin.park@samsung.com, k.kozlowski@samsung.com, kgene@kernel.org Subject: [PATCH v8 01/20] PM / devfreq: exynos: Add generic exynos bus frequency driver Date: Fri, 08 Apr 2016 13:24:50 +0900 Message-id: <1460089509-16260-2-git-send-email-cw00.choi@samsung.com> X-Mailer: git-send-email 1.8.0 In-reply-to: <1460089509-16260-1-git-send-email-cw00.choi@samsung.com> References: <1460089509-16260-1-git-send-email-cw00.choi@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFjrAIsWRmVeSWpSXmKPExsWyRsSkWHe5EXu4weedOhbXvzxntZh/5Byr Rf+bhawW516tZLSYdH8Ci8XrF4YW/Y9fM1ucbXrDbrHp8TVWi8u75rBZfO49wmgx4/w+Jot1 G2+xW9y+zGvx8sgPRoul1y8yWdxuXMFmMWH6WhaLM6cvsVq07j3CbtG2+gOrg6jHmnlrGD1a mnvYPC739TJ53LpT77Fz1l12j5XLv7B5bFrVyeaxeUm9x79j7B5brrazePRtWcXo8XmTXABP FJdNSmpOZllqkb5dAldG99OLrAWHSyoOzXrJ3MB4P66LkYNDQsBEYslxyS5GTiBTTOLCvfVs XYxcHEICKxglLk86xQiRMJH4tOMrE0RiFqPEp+tPGSGcL4wSXw5eYAOpYhPQktj/4gaYLSKQ IvH44UmwUcwCR5glpm68wQSSEBaIlNj06hEriM0ioCrxsusYC4jNK+AqsXTVNqh1chIf9jxi B7E5BdwkJl+ZxAxiCwHV3Ng8nRlkqITAFg6Jk8cXMUMMEpD4NvkQC8Q/shKbDjBDzJGUOLji BssERuEFjAyrGEVTC5ILipPSi4z0ihNzi0vz0vWS83M3MQIj9vS/Z307GG8esD7EKMDBqMTD e+E9W7gQa2JZcWXuIUZToA0TmaVEk/OBaSGvJN7Q2MzIwtTE1NjI3NJMSZw3QepnsJBAemJJ anZqakFqUXxRaU5q8SFGJg5OqQbGlqfJqhvrJU25BOU5YyzXhvtlBWv+ecco21l/ZuMfgUld mr/+iT34OOuT0SSOVY1TFhssbnHZGb0wRIqJ8VCPorFE+tnVEf15mlsmGL27FpS98micVKp4 rvDe36ldE/W8e7c9zO5o94q4V1a4vpGtbvnMq96JolUntt9lcbvzbe6LwHtpu9crsRRnJBpq MRcVJwIASf+O6dMCAAA= X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFupjleLIzCtJLcpLzFFi42I5/e+xoO5yI/Zwg9ZNyhbXvzxntZh/5Byr Rf+bhawW516tZLSYdH8Ci8XrF4YW/Y9fM1ucbXrDbrHp8TVWi8u75rBZfO49wmgx4/w+Jot1 G2+xW9y+zGvx8sgPRoul1y8yWdxuXMFmMWH6WhaLM6cvsVq07j3CbtG2+gOrg6jHmnlrGD1a mnvYPC739TJ53LpT77Fz1l12j5XLv7B5bFrVyeaxeUm9x79j7B5brrazePRtWcXo8XmTXABP VAOjTUZqYkpqkUJqXnJ+SmZeuq2Sd3C8c7ypmYGhrqGlhbmSQl5ibqqtkotPgK5bZg7Qr0oK ZYk5pUChgMTiYiV9O0wTQkPcdC1gGiN0fUOC4HqMDNBAwhrGjO6nF1kLDpdUHJr1krmB8X5c FyMnh4SAicSnHV+ZIGwxiQv31rN1MXJxCAnMYpT4dP0pI4TzhVHiy8ELbCBVbAJaEvtf3ACz RQRSJB4/PAnWwSxwhFli6sYbYKOEBSIlNr16xApiswioSrzsOsYCYvMKuEosXbWNEWKdnMSH PY/YQWxOATeJyVcmMYPYQkA1NzZPZ57AyLuAkWEVo0RqQXJBcVJ6rlFearlecWJucWleul5y fu4mRnBaeCa9g/HwLvdDjAIcjEo8vBfes4ULsSaWFVfmHmKU4GBWEuF11WcPF+JNSaysSi3K jy8qzUktPsRoCnTYRGYp0eR8YMrKK4k3NDYxM7I0Mje0MDI2VxLnffx/XZiQQHpiSWp2ampB ahFMHxMHp1QDY+6fIq9ax0/bf1b4MfkW862Yz+Iq+exn0+x9b6ad4/4omW/2SGGZnd3L64rm 1tYqDoVXP+/WnCRfxuHPe2A2c5CWW6HYt8Jnp7eIzGDKKJnasoE7Zn3amg+1bxROcXEVrK4O F7tY25JxfN5+Pbtd8qXMfh+/97F+eSJ5+OonKbkjAqyX7RtnKLEUZyQaajEXFScCADz49/4h AwAA 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-20160407_212535_482977_9E65AC8D X-CRM114-Status: GOOD ( 28.44 ) X-Spam-Score: -7.9 (-------) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: mark.rutland@arm.com, inki.dae@samsung.com, linux-samsung-soc@vger.kernel.org, linux@arm.linux.org.uk, cw00.choi@samsung.com, pawel.moll@arm.com, ijc+devicetree@hellion.org.uk, linux.amoon@gmail.com, linux-pm@vger.kernel.org, rjw@rjwysocki.net, linux-kernel@vger.kernel.org, m.reichl@fivetechno.de, tjakobi@math.uni-bielefeld.de, devicetree@vger.kernel.org, robh+dt@kernel.org, galak@codeaurora.org, linux-arm-kernel@lists.infradead.org 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=-5.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, 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 adds the generic exynos bus frequency driver for AMBA AXI bus of sub-blocks in exynos SoC with DEVFREQ framework. The Samsung Exynos SoC have the common architecture for bus between DRAM and sub-blocks in SoC. This driver can support the generic bus frequency driver for Exynos SoCs. In devicetree, Each bus block has a bus clock, regulator, operation-point and devfreq-event devices which measure the utilization of each bus block. Signed-off-by: Chanwoo Choi [m.reichl and linux.amoon: Tested it on exynos4412-odroidu3 board] Tested-by: Markus Reichl Tested-by: Anand Moon Signed-off-by: MyungJoo Ham --- drivers/devfreq/Kconfig | 15 ++ drivers/devfreq/Makefile | 1 + drivers/devfreq/exynos-bus.c | 443 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 drivers/devfreq/exynos-bus.c diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 4de78c552251..cedda8f1e5eb 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -66,6 +66,21 @@ config DEVFREQ_GOV_USERSPACE comment "DEVFREQ Drivers" +config ARM_EXYNOS_BUS_DEVFREQ + bool "ARM EXYNOS Generic Memory Bus DEVFREQ Driver" + depends on ARCH_EXYNOS + select DEVFREQ_GOV_SIMPLE_ONDEMAND + select DEVFREQ_EVENT_EXYNOS_PPMU + select PM_DEVFREQ_EVENT + select PM_OPP + help + This adds the common DEVFREQ driver for Exynos Memory bus. Exynos + Memory bus has one more group of memory bus (e.g, MIF and INT block). + Each memory bus group could contain many memoby bus block. It reads + PPMU counters of memory controllers by using DEVFREQ-event device + and adjusts the operating frequencies and voltages with OPP support. + This does not yet operate with optimal voltages. + config ARM_EXYNOS4_BUS_DEVFREQ bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver" depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 5134f9ee983d..8af8aaf922a8 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o # DEVFREQ Drivers +obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/ obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/ obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c new file mode 100644 index 000000000000..137de6196af3 --- /dev/null +++ b/drivers/devfreq/exynos-bus.c @@ -0,0 +1,443 @@ +/* + * Generic Exynos Bus frequency driver with DEVFREQ Framework + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi + * + * This driver support Exynos Bus frequency feature by using + * DEVFREQ framework and is based on drivers/devfreq/exynos/exynos4_bus.c. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_SATURATION_RATIO 40 +#define DEFAULT_VOLTAGE_TOLERANCE 2 + +struct exynos_bus { + struct device *dev; + + struct devfreq *devfreq; + struct devfreq_event_dev **edev; + unsigned int edev_count; + struct mutex lock; + + struct dev_pm_opp *curr_opp; + + struct regulator *regulator; + struct clk *clk; + unsigned int voltage_tolerance; + unsigned int ratio; +}; + +/* + * Control the devfreq-event device to get the current state of bus + */ +#define exynos_bus_ops_edev(ops) \ +static int exynos_bus_##ops(struct exynos_bus *bus) \ +{ \ + int i, ret; \ + \ + for (i = 0; i < bus->edev_count; i++) { \ + if (!bus->edev[i]) \ + continue; \ + ret = devfreq_event_##ops(bus->edev[i]); \ + if (ret < 0) \ + return ret; \ + } \ + \ + return 0; \ +} +exynos_bus_ops_edev(enable_edev); +exynos_bus_ops_edev(disable_edev); +exynos_bus_ops_edev(set_event); + +static int exynos_bus_get_event(struct exynos_bus *bus, + struct devfreq_event_data *edata) +{ + struct devfreq_event_data event_data; + unsigned long load_count = 0, total_count = 0; + int i, ret = 0; + + for (i = 0; i < bus->edev_count; i++) { + if (!bus->edev[i]) + continue; + + ret = devfreq_event_get_event(bus->edev[i], &event_data); + if (ret < 0) + return ret; + + if (i == 0 || event_data.load_count > load_count) { + load_count = event_data.load_count; + total_count = event_data.total_count; + } + } + + edata->load_count = load_count; + edata->total_count = total_count; + + return ret; +} + +/* + * Must necessary function for devfreq governor + */ +static int exynos_bus_target(struct device *dev, unsigned long *freq, u32 flags) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + struct dev_pm_opp *new_opp; + unsigned long old_freq, new_freq, old_volt, new_volt, tol; + int ret = 0; + + /* Get new opp-bus instance according to new bus clock */ + rcu_read_lock(); + new_opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(new_opp)) { + dev_err(dev, "failed to get recommended opp instance\n"); + rcu_read_unlock(); + return PTR_ERR(new_opp); + } + + new_freq = dev_pm_opp_get_freq(new_opp); + new_volt = dev_pm_opp_get_voltage(new_opp); + old_freq = dev_pm_opp_get_freq(bus->curr_opp); + old_volt = dev_pm_opp_get_voltage(bus->curr_opp); + rcu_read_unlock(); + + if (old_freq == new_freq) + return 0; + tol = new_volt * bus->voltage_tolerance / 100; + + /* Change voltage and frequency according to new OPP level */ + mutex_lock(&bus->lock); + + if (old_freq < new_freq) { + ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol); + if (ret < 0) { + dev_err(bus->dev, "failed to set voltage\n"); + goto out; + } + } + + ret = clk_set_rate(bus->clk, new_freq); + if (ret < 0) { + dev_err(dev, "failed to change clock of bus\n"); + clk_set_rate(bus->clk, old_freq); + goto out; + } + + if (old_freq > new_freq) { + ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol); + if (ret < 0) { + dev_err(bus->dev, "failed to set voltage\n"); + goto out; + } + } + bus->curr_opp = new_opp; + + dev_dbg(dev, "Set the frequency of bus (%lukHz -> %lukHz)\n", + old_freq/1000, new_freq/1000); +out: + mutex_unlock(&bus->lock); + + return ret; +} + +static int exynos_bus_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + struct devfreq_event_data edata; + int ret; + + rcu_read_lock(); + stat->current_frequency = dev_pm_opp_get_freq(bus->curr_opp); + rcu_read_unlock(); + + ret = exynos_bus_get_event(bus, &edata); + if (ret < 0) { + stat->total_time = stat->busy_time = 0; + goto err; + } + + stat->busy_time = (edata.load_count * 100) / bus->ratio; + stat->total_time = edata.total_count; + + dev_dbg(dev, "Usage of devfreq-event : %lu/%lu\n", stat->busy_time, + stat->total_time); + +err: + ret = exynos_bus_set_event(bus); + if (ret < 0) { + dev_err(dev, "failed to set event to devfreq-event devices\n"); + return ret; + } + + return ret; +} + +static void exynos_bus_exit(struct device *dev) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + int ret; + + ret = exynos_bus_disable_edev(bus); + if (ret < 0) + dev_warn(dev, "failed to disable the devfreq-event devices\n"); + + if (bus->regulator) + regulator_disable(bus->regulator); + + dev_pm_opp_of_remove_table(dev); +} + +static int exynos_bus_parse_of(struct device_node *np, + struct exynos_bus *bus) +{ + struct device *dev = bus->dev; + unsigned long rate; + int i, ret, count, size; + + /* Get the clock to provide each bus with source clock */ + bus->clk = devm_clk_get(dev, "bus"); + if (IS_ERR(bus->clk)) { + dev_err(dev, "failed to get bus clock\n"); + return PTR_ERR(bus->clk); + } + + ret = clk_prepare_enable(bus->clk); + if (ret < 0) { + dev_err(dev, "failed to get enable clock\n"); + return ret; + } + + /* Get the freq/voltage OPP table to scale the bus frequency */ + rcu_read_lock(); + ret = dev_pm_opp_of_add_table(dev); + if (ret < 0) { + dev_err(dev, "failed to get OPP table\n"); + rcu_read_unlock(); + goto err_clk; + } + + rate = clk_get_rate(bus->clk); + bus->curr_opp = dev_pm_opp_find_freq_ceil(dev, &rate); + if (IS_ERR(bus->curr_opp)) { + dev_err(dev, "failed to find dev_pm_opp\n"); + rcu_read_unlock(); + ret = PTR_ERR(bus->curr_opp); + goto err_opp; + } + rcu_read_unlock(); + + /* Get the regulator to provide each bus with the power */ + bus->regulator = devm_regulator_get(dev, "vdd"); + if (IS_ERR(bus->regulator)) { + dev_err(dev, "failed to get VDD regulator\n"); + ret = PTR_ERR(bus->regulator); + goto err_opp; + } + + ret = regulator_enable(bus->regulator); + if (ret < 0) { + dev_err(dev, "failed to enable VDD regulator\n"); + goto err_opp; + } + + /* + * Get the devfreq-event devices to get the current utilization of + * buses. This raw data will be used in devfreq ondemand governor. + */ + count = devfreq_event_get_edev_count(dev); + if (count < 0) { + dev_err(dev, "failed to get the count of devfreq-event dev\n"); + ret = count; + goto err_regulator; + } + bus->edev_count = count; + + size = sizeof(*bus->edev) * count; + bus->edev = devm_kzalloc(dev, size, GFP_KERNEL); + if (!bus->edev) { + ret = -ENOMEM; + goto err_regulator; + } + + for (i = 0; i < count; i++) { + bus->edev[i] = devfreq_event_get_edev_by_phandle(dev, i); + if (IS_ERR(bus->edev[i])) { + ret = -EPROBE_DEFER; + goto err_regulator; + } + } + + /* + * Optionally, Get the saturation ratio according to Exynos SoC + * When measuring the utilization of each AXI bus with devfreq-event + * devices, the measured real cycle might be much lower than the + * total cycle of bus during sampling rate. In result, the devfreq + * simple-ondemand governor might not decide to change the current + * frequency due to too utilization (= real cycle/total cycle). + * So, this property is used to adjust the utilization when calculating + * the busy_time in exynos_bus_get_dev_status(). + */ + if (of_property_read_u32(np, "exynos,saturation-ratio", &bus->ratio)) + bus->ratio = DEFAULT_SATURATION_RATIO; + + if (of_property_read_u32(np, "exynos,voltage-tolerance", + &bus->voltage_tolerance)) + bus->voltage_tolerance = DEFAULT_VOLTAGE_TOLERANCE; + + return 0; + +err_regulator: + regulator_disable(bus->regulator); +err_opp: + dev_pm_opp_of_remove_table(dev); +err_clk: + clk_disable_unprepare(bus->clk); + + return ret; +} + +static int exynos_bus_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct devfreq_dev_profile *profile; + struct devfreq_simple_ondemand_data *ondemand_data; + struct exynos_bus *bus; + int ret; + + if (!np) { + dev_err(dev, "failed to find devicetree node\n"); + return -EINVAL; + } + + bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); + if (!bus) + return -ENOMEM; + mutex_init(&bus->lock); + bus->dev = &pdev->dev; + platform_set_drvdata(pdev, bus); + + /* Parse the device-tree to get the resource information */ + ret = exynos_bus_parse_of(np, bus); + if (ret < 0) + return ret; + + /* Initalize the struct profile and governor data */ + profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL); + if (!profile) + return -ENOMEM; + profile->polling_ms = 50; + profile->target = exynos_bus_target; + profile->get_dev_status = exynos_bus_get_dev_status; + profile->exit = exynos_bus_exit; + + ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL); + if (!ondemand_data) + return -ENOMEM; + ondemand_data->upthreshold = 40; + ondemand_data->downdifferential = 5; + + /* Add devfreq device to monitor and handle the exynos bus */ + bus->devfreq = devm_devfreq_add_device(dev, profile, "simple_ondemand", + ondemand_data); + if (IS_ERR(bus->devfreq)) { + dev_err(dev, "failed to add devfreq device\n"); + return PTR_ERR(bus->devfreq); + } + + /* Register opp_notifier to catch the change of OPP */ + ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq); + if (ret < 0) { + dev_err(dev, "failed to register opp notifier\n"); + return ret; + } + + /* + * Enable devfreq-event to get raw data which is used to determine + * current bus load. + */ + ret = exynos_bus_enable_edev(bus); + if (ret < 0) { + dev_err(dev, "failed to enable devfreq-event devices\n"); + return ret; + } + + ret = exynos_bus_set_event(bus); + if (ret < 0) { + dev_err(dev, "failed to set event to devfreq-event devices\n"); + return ret; + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos_bus_resume(struct device *dev) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + int ret; + + ret = exynos_bus_enable_edev(bus); + if (ret < 0) { + dev_err(dev, "failed to enable the devfreq-event devices\n"); + return ret; + } + + return 0; +} + +static int exynos_bus_suspend(struct device *dev) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + int ret; + + ret = exynos_bus_disable_edev(bus); + if (ret < 0) { + dev_err(dev, "failed to disable the devfreq-event devices\n"); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops exynos_bus_pm = { + SET_SYSTEM_SLEEP_PM_OPS(exynos_bus_suspend, exynos_bus_resume) +}; + +static const struct of_device_id exynos_bus_of_match[] = { + { .compatible = "samsung,exynos-bus", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, exynos_bus_of_match); + +static struct platform_driver exynos_bus_platdrv = { + .probe = exynos_bus_probe, + .driver = { + .name = "exynos-bus", + .pm = &exynos_bus_pm, + .of_match_table = of_match_ptr(exynos_bus_of_match), + }, +}; +module_platform_driver(exynos_bus_platdrv); + +MODULE_DESCRIPTION("Generic Exynos Bus frequency driver"); +MODULE_AUTHOR("Chanwoo Choi "); +MODULE_LICENSE("GPL v2");