From patchwork Wed Oct 22 07:53:42 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "jinkun.hong" X-Patchwork-Id: 5130091 Return-Path: X-Original-To: patchwork-linux-rockchip@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id DCB7BC11AC for ; Wed, 22 Oct 2014 07:55:19 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id A458120166 for ; Wed, 22 Oct 2014 07:55:18 +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 6C45F20154 for ; Wed, 22 Oct 2014 07:55:17 +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 1XgqlJ-0006LL-3I; Wed, 22 Oct 2014 07:55:17 +0000 Received: from mail-pd0-f195.google.com ([209.85.192.195]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Xgql4-000574-No; Wed, 22 Oct 2014 07:55:04 +0000 Received: by mail-pd0-f195.google.com with SMTP id ft15so1089491pdb.2 for ; Wed, 22 Oct 2014 00:54:41 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=2K97f0T/qoorPvqoeZhfY4zlC0vPyObxuUqovcSatoc=; b=BEYLm7D6ZtNlE1xTpkd8Q5iU8AhTiVGJ75x8WYDD677Z4uUi+p8i1fBOI0bIUm103p v4hv7tqbSalTyAaJuAkm9aJGxPzJhbi59E4I8Jb8AkEILlxpPHXbbYWJw/HqSHEWQTz8 hjqlbOfYay6uH5sXZAM2VcoILNadgr6tgtq/f+eR3MeaeZXbco+Uc5/gMQfuYon9Fi6G zsxTfByUhqar+l21A2fHQInGFm0iZ0BJ1Os+Ts3/2y9UrYHuwSwQg/shekMrUB/DNdSB 8zFA/6a572mNuN89sE44OSOnSjmrfGVsWNOWu3U+mPR7TS1u2MB9VoAaSQiu7kJtEsY9 6O0g== X-Received: by 10.70.64.231 with SMTP id r7mr17153444pds.42.1413964481885; Wed, 22 Oct 2014 00:54:41 -0700 (PDT) Received: from localhost.localdomain ([58.22.7.114]) by mx.google.com with ESMTPSA id ou4sm3316862pdb.51.2014.10.22.00.54.32 for (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Wed, 22 Oct 2014 00:54:41 -0700 (PDT) From: "jinkun.hong" To: linus.walleij@linaro.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH v6 1/3] power-domain: add power domain drivers for Rockchip platform Date: Wed, 22 Oct 2014 00:53:42 -0700 Message-Id: <1413964424-7599-2-git-send-email-jinkun.hong@rock-chips.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1413964424-7599-1-git-send-email-jinkun.hong@rock-chips.com> References: <1413964424-7599-1-git-send-email-jinkun.hong@rock-chips.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20141022_005502_894944_473C9BAD X-CRM114-Status: GOOD ( 20.22 ) X-Spam-Score: -1.1 (-) Cc: Mark Rutland , devicetree@vger.kernel.org, Ulf Hansson , Russell King , Heiko Stuebner , Pawel Moll , Ian Campbell , "jinkun.hong" , Randy Dunlap , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, dianders@chromium.org, linux-rockchip@lists.infradead.org, Rob Herring , Kumar Gala , Grant Likely , Jack Dai X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+patchwork-linux-rockchip=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-3.3 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_NONE, 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 From: "jinkun.hong" Add power domain drivers based on generic power domain for Rockchip platform, and support RK3288. Signed-off-by: Jack Dai Signed-off-by: jinkun.hong --- Changes in v6: - delete pmu_lock - modify dev_lock using mutex - pm_clk_resume(pd->dev) change to pm_clk_resume(ed->dev) - pm_clk_suspend(pd->dev) change to pm_clk_suspend(ed->dev) - add devm_kfree(pd->dev, de) in rockchip_pm_domain_detach_dev Changes in v5: - delete idle_lock - add timeout in rockchip_pmu_set_idle_request() Changes in v4: - use list storage dev Changes in v3: - change use pm_clk_resume() and pm_clk_suspend() Changes in v2: - remove the "pd->pd.of_node = np" arch/arm/mach-rockchip/Kconfig | 1 + arch/arm/mach-rockchip/Makefile | 1 + arch/arm/mach-rockchip/pm_domains.c | 358 +++++++++++++++++++++++++++++++++++ 3 files changed, 360 insertions(+) create mode 100644 arch/arm/mach-rockchip/pm_domains.c diff --git a/arch/arm/mach-rockchip/Kconfig b/arch/arm/mach-rockchip/Kconfig index d168669..4920a88 100644 --- a/arch/arm/mach-rockchip/Kconfig +++ b/arch/arm/mach-rockchip/Kconfig @@ -12,6 +12,7 @@ config ARCH_ROCKCHIP select DW_APB_TIMER_OF select ARM_GLOBAL_TIMER select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK + select PM_GENERIC_DOMAINS if PM help Support for Rockchip's Cortex-A9 Single-to-Quad-Core-SoCs containing the RK2928, RK30xx and RK31xx series. diff --git a/arch/arm/mach-rockchip/Makefile b/arch/arm/mach-rockchip/Makefile index b29d8ea..805268d 100644 --- a/arch/arm/mach-rockchip/Makefile +++ b/arch/arm/mach-rockchip/Makefile @@ -2,3 +2,4 @@ CFLAGS_platsmp.o := -march=armv7-a obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip.o obj-$(CONFIG_SMP) += headsmp.o platsmp.o +obj-$(CONFIG_PM_GENERIC_DOMAINS) += pm_domains.o diff --git a/arch/arm/mach-rockchip/pm_domains.c b/arch/arm/mach-rockchip/pm_domains.c new file mode 100644 index 0000000..c78342f --- /dev/null +++ b/arch/arm/mach-rockchip/pm_domains.c @@ -0,0 +1,358 @@ +/* + * Rockchip Generic power domain support. + * + * Copyright (c) 2014 ROCKCHIP, Co. Ltd. + * Author: Hong Jinkun + * + * 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 +#include +#include +#include + +#define PWR_OFFSET 0x08 +#define STATUS_OFFSET 0x0c +#define REQ_OFFSET 0x10 +#define IDLE_OFFSET 0x14 +#define ACK_OFFSET 0x14 +#define PMU_TIMEOUT_MAX 10000 + +struct rockchip_dev_entry { + struct list_head node; + struct device *dev; +}; + +struct rockchip_domain { + struct generic_pm_domain base; + struct device *dev; + struct regmap *regmap_pmu; + struct list_head dev_list; + /* mutex lock for dev_list */ + struct mutex dev_lock; + u32 pwr_shift; + u32 status_shift; + u32 req_shift; + u32 idle_shift; + u32 ack_shift; +}; + +#define to_rockchip_pd(_gpd) container_of(_gpd, struct rockchip_domain, base) + +static int rockchip_pmu_set_idle_request(struct rockchip_domain *pd, + bool idle) +{ + u32 idle_mask = BIT(pd->idle_shift); + u32 idle_target = idle << (pd->idle_shift); + u32 ack_mask = BIT(pd->ack_shift); + u32 ack_target = idle << (pd->ack_shift); + unsigned int mask = BIT(pd->req_shift); + unsigned int val; + unsigned long flags; + int timeout = 0; + + val = (idle) ? mask : 0; + regmap_update_bits(pd->regmap_pmu, REQ_OFFSET, mask, val); + dsb(); + + do { + regmap_read(pd->regmap_pmu, ACK_OFFSET, &val); + udelay(1); + if (timeout > PMU_TIMEOUT_MAX) { + pr_err("%s wait pmu ack timeout!\n", __func__); + break; + } + timeout += 1; + } while ((val & ack_mask) != ack_target); + + timeout = 0; + + do { + regmap_read(pd->regmap_pmu, IDLE_OFFSET, &val); + udelay(1); + if (timeout > PMU_TIMEOUT_MAX) { + pr_err("%s wait pmu idle timeout!\n", __func__); + break; + } + timeout += 1; + } while ((val & idle_mask) != idle_target); + + return 0; +} + +static bool rockchip_pmu_power_domain_is_on(struct rockchip_domain *pd) +{ + unsigned int val; + + regmap_read(pd->regmap_pmu, STATUS_OFFSET, &val); + + /* 1'b0: power on, 1'b1: power off */ + return !(val & BIT(pd->status_shift)); +} + +static void rockchip_do_pmu_set_power_domain( + struct rockchip_domain *pd, bool on) +{ + unsigned int mask = BIT(pd->pwr_shift); + unsigned int val; + + val = (on) ? 0 : mask; + regmap_update_bits(pd->regmap_pmu, PWR_OFFSET, mask, val); + dsb(); + + do { + regmap_read(pd->regmap_pmu, STATUS_OFFSET, &val); + } while ((val & BIT(pd->status_shift)) == on); +} + +static int rockchip_pmu_set_power_domain(struct rockchip_domain *pd, + bool on) +{ + unsigned long flags; + + if (rockchip_pmu_power_domain_is_on(pd) == on) + return 0; + + if (!on) { + /* FIXME: add code to save AXI_QOS */ + /* if power down, idle request to NIU first */ + rockchip_pmu_set_idle_request(pd, true); + } + + rockchip_do_pmu_set_power_domain(pd, on); + + if (on) { + /* if power up, idle request release to NIU */ + rockchip_pmu_set_idle_request(pd, false); + /* FIXME: add code to restore AXI_QOS */ + } + + return 0; +} + +static int rockchip_pd_power(struct rockchip_domain *pd, bool power_on) +{ + int ret = 0; + struct rockchip_dev_entry *de; + + mutex_lock(&pd->dev_lock); + /* no clk, set power domain will fail */ + if (list_empty(&pd->dev_list)) { + pr_err("%s: no devices in %s power domain\n", __func__, + pd->base.name); + goto out; + } + + list_for_each_entry(de, &pd->dev_list, node) { + pm_clk_resume(de->dev); + } + + ret = rockchip_pmu_set_power_domain(pd, power_on); + + list_for_each_entry(de, &pd->dev_list, node) { + pm_clk_suspend(de->dev); + } +out: + mutex_unlock(&pd->dev_lock); + return ret; +} + +static int rockchip_pd_power_on(struct generic_pm_domain *domain) +{ + struct rockchip_domain *pd = to_rockchip_pd(domain); + + return rockchip_pd_power(pd, true); +} + +static int rockchip_pd_power_off(struct generic_pm_domain *domain) +{ + struct rockchip_domain *pd = to_rockchip_pd(domain); + + return rockchip_pd_power(pd, false); +} + +void rockchip_pm_domain_attach_dev(struct device *dev) +{ + int ret; + int i = 0; + struct clk *clk; + struct rockchip_domain *pd; + struct rockchip_dev_entry *de; + + pd = (struct rockchip_domain *)dev->pm_domain; + ret = pm_clk_create(dev); + if (ret) { + dev_err(dev, "pm_clk_create failed %d\n", ret); + return; + } + + while ((clk = of_clk_get(dev->of_node, i++)) && !IS_ERR(clk)) { + ret = pm_clk_add_clk(dev, clk); + if (ret) { + dev_err(dev, "pm_clk_add_clk failed %d\n", ret); + pm_clk_destroy(dev); + return; + } + } + + de = devm_kzalloc(pd->dev, + sizeof(struct rockchip_dev_entry *), GFP_KERNEL); + de->dev = dev; + mutex_lock(&pd->dev_lock); + list_add_tail(&de->node, &pd->dev_list); + mutex_unlock(&pd->dev_lock); + + return; +} + +void rockchip_pm_domain_detach_dev(struct device *dev) +{ + struct rockchip_domain *pd; + struct rockchip_dev_entry *de; + + pd = (struct rockchip_domain *)dev->pm_domain; + mutex_lock(&pd->dev_lock); + + list_for_each_entry(de, &pd->dev_list, node) { + if (de->dev == dev) { + list_del(&de->node); + pm_clk_destroy(dev); + devm_kfree(pd->dev, de); + } + } + + mutex_unlock(&pd->dev_lock); +} + +static const struct of_device_id rockchip_pm_domain_dt_match[]; + +static int rockchip_pm_domain_probe(struct platform_device *pdev) +{ + struct device_node *node; + struct regmap *regmap_pmu; + struct rockchip_domain *pd; + const struct of_device_id *match; + + match = of_match_node(rockchip_pm_domain_dt_match, pdev->dev.of_node); + pd = (struct rockchip_domain *)match->data; + if (!pd) + return -ENOMEM; + + node = of_parse_phandle(pdev->dev.of_node, "rockchip,pmu", 0); + regmap_pmu = syscon_node_to_regmap(node); + of_node_put(node); + if (IS_ERR(regmap_pmu)) { + pr_err("%s: failed to get regmap_pmu", __func__); + return PTR_ERR(regmap_pmu); + } + + pd->regmap_pmu = regmap_pmu; + pd->dev = &pdev->dev; + + INIT_LIST_HEAD(&pd->dev_list); + mutex_init(&pd->dev_lock); + pm_genpd_init(&pd->base, NULL, false); + + return of_genpd_add_provider_simple(pdev->dev.of_node, &pd->base); +} + +static struct rockchip_domain gpu_domain = { + .base = { + .name = "pd_gpu", + .attach_dev = rockchip_pm_domain_attach_dev, + .detach_dev = rockchip_pm_domain_detach_dev, + .power_off = rockchip_pd_power_off, + .power_on = rockchip_pd_power_on, + }, + .pwr_shift = 9, + .status_shift = 9, + .req_shift = 2, + .idle_shift = 2, + .ack_shift = 18, +}; + +static struct rockchip_domain hevc_domain = { + .base = { + .name = "pd_hevc", + .attach_dev = rockchip_pm_domain_attach_dev, + .detach_dev = rockchip_pm_domain_detach_dev, + .power_off = rockchip_pd_power_off, + .power_on = rockchip_pd_power_on, + }, + .pwr_shift = 14, + .status_shift = 10, + .req_shift = 9, + .idle_shift = 9, + .ack_shift = 25, + +}; + +static struct rockchip_domain video_domain = { + .base = { + .name = "pd_video", + .attach_dev = rockchip_pm_domain_attach_dev, + .detach_dev = rockchip_pm_domain_detach_dev, + .power_off = rockchip_pd_power_off, + .power_on = rockchip_pd_power_on, + }, + .pwr_shift = 8, + .status_shift = 8, + .req_shift = 3, + .idle_shift = 3, + .ack_shift = 19, +}; + +static struct rockchip_domain vio_domain = { + .base = { + .name = "pd_vio", + .attach_dev = rockchip_pm_domain_attach_dev, + .detach_dev = rockchip_pm_domain_detach_dev, + .power_off = rockchip_pd_power_off, + .power_on = rockchip_pd_power_on, + }, + .pwr_shift = 7, + .status_shift = 7, + .req_shift = 4, + .idle_shift = 4, + .ack_shift = 20, +}; + +static const struct of_device_id rockchip_pm_domain_dt_match[] = { + { .compatible = "rockchip,rk3288-power-gpu", + .data = (void *)&gpu_domain}, + { .compatible = "rockchip,rk3288-power-hevc", + .data = (void *)&hevc_domain}, + { .compatible = "rockchip,rk3288-power-video", + .data = (void *)&video_domain}, + { .compatible = "rockchip,rk3288-power-vio", + .data = (void *)&vio_domain}, + {}, +}; +MODULE_DEVICE_TABLE(of, rockchip_pm_domain_dt_match); + +static struct platform_driver rockchip_pm_domain_driver = { + .probe = rockchip_pm_domain_probe, + .driver = { + .name = "rockchip-pm-domain", + .owner = THIS_MODULE, + .of_match_table = rockchip_pm_domain_dt_match, + }, +}; + +static int __init rockchip_pm_domain_drv_register(void) +{ + return platform_driver_register(&rockchip_pm_domain_driver); +} +postcore_initcall(rockchip_pm_domain_drv_register);