From patchwork Fri Apr 4 09:48:45 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Lezcano X-Patchwork-Id: 27798 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-ve0-f199.google.com (mail-ve0-f199.google.com [209.85.128.199]) by ip-10-151-82-157.ec2.internal (Postfix) with ESMTPS id 983AF20369 for ; Fri, 4 Apr 2014 09:48:36 +0000 (UTC) Received: by mail-ve0-f199.google.com with SMTP id jy13sf5392123veb.2 for ; Fri, 04 Apr 2014 02:48:36 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:delivered-to:from:to:cc:subject :date:message-id:sender:precedence:list-id:x-original-sender :x-original-authentication-results:mailing-list:list-post:list-help :list-archive:list-unsubscribe; bh=VVDaekzM+HOFU27MUJkGdlJIZsOPc6FXclJIiB96i9A=; b=F/IbWEtCqB4QsdEsgRTmwHlyhOPAxQLyUOoXUsltEQzOJh8UvGOzYoT1jTibOiCZKn wid/ZJdLkqioreDXlmm0L7kIgvzJG+kEFW2tU+EYVM+O6sxLxriTRpRIx0scrAuP6IVy qqg1jHP2h+JB8FkRrBP/6MAeFwK/OEpntrUvO7GogqRVYX9933Um6M+mQklED3GruLqE AK2OrZbDNbKVvQoOEXB4cpWpvf54M/DTh7wQRggZ97/jSNTCnZXPh5Cg0A3OzGgj20Su e1xCoseM1qdhrXyjYYh1qWIQ7e8J+ZXnoClgEeS2Rdfxo14IyJ5ttfPJnP/wXWA0acNC +ymQ== X-Gm-Message-State: ALoCoQlnsgEbLxjeZtc8GXLti0EO3YrYZ3XtR7fPgCdDCJoazzaPe555CESMGtAsshhbSRqNLtVE X-Received: by 10.224.113.202 with SMTP id b10mr7278152qaq.3.1396604916258; Fri, 04 Apr 2014 02:48:36 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: patchwork-forward@linaro.org Received: by 10.140.90.80 with SMTP id w74ls966726qgd.95.gmail; Fri, 04 Apr 2014 02:48:36 -0700 (PDT) X-Received: by 10.220.250.203 with SMTP id mp11mr7511262vcb.2.1396604916040; Fri, 04 Apr 2014 02:48:36 -0700 (PDT) Received: from mail-ve0-f169.google.com (mail-ve0-f169.google.com [209.85.128.169]) by mx.google.com with ESMTPS id sr19si1697980vcb.179.2014.04.04.02.48.36 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 04 Apr 2014 02:48:36 -0700 (PDT) Received-SPF: neutral (google.com: 209.85.128.169 is neither permitted nor denied by best guess record for domain of patch+caf_=patchwork-forward=linaro.org@linaro.org) client-ip=209.85.128.169; Received: by mail-ve0-f169.google.com with SMTP id pa12so1287472veb.28 for ; Fri, 04 Apr 2014 02:48:36 -0700 (PDT) X-Received: by 10.52.175.166 with SMTP id cb6mr10242820vdc.1.1396604915932; Fri, 04 Apr 2014 02:48:35 -0700 (PDT) X-Forwarded-To: patchwork-forward@linaro.org X-Forwarded-For: patch@linaro.org patchwork-forward@linaro.org Delivered-To: patch@linaro.org Received: by 10.220.12.8 with SMTP id v8csp99799vcv; Fri, 4 Apr 2014 02:48:35 -0700 (PDT) X-Received: by 10.66.150.169 with SMTP id uj9mr13894731pab.148.1396604914572; Fri, 04 Apr 2014 02:48:34 -0700 (PDT) Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id bb4si4371990pbc.353.2014.04.04.02.48.33; Fri, 04 Apr 2014 02:48:34 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-samsung-soc-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752535AbaDDJsd (ORCPT + 9 others); Fri, 4 Apr 2014 05:48:33 -0400 Received: from mail-wg0-f48.google.com ([74.125.82.48]:56152 "EHLO mail-wg0-f48.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752528AbaDDJsb (ORCPT ); Fri, 4 Apr 2014 05:48:31 -0400 Received: by mail-wg0-f48.google.com with SMTP id l18so3194192wgh.7 for ; Fri, 04 Apr 2014 02:48:29 -0700 (PDT) X-Received: by 10.180.97.72 with SMTP id dy8mr3015194wib.5.1396604909774; Fri, 04 Apr 2014 02:48:29 -0700 (PDT) Received: from localhost.localdomain (AToulouse-654-1-451-172.w83-205.abo.wanadoo.fr. [83.205.74.172]) by mx.google.com with ESMTPSA id jd2sm3610023wic.9.2014.04.04.02.48.28 for (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 04 Apr 2014 02:48:29 -0700 (PDT) From: Daniel Lezcano To: kgene.kim@samsung.com, t.figa@samsung.com Cc: linux-samsung-soc@vger.kernel.org, linaro-kernel@lists.linaro.org, linux-arm-kernel@lists.infradead.org, linux-pm@vger.kernel.org, ccross@google.com Subject: [PATCH] Exynos4: cpuidle: support dual CPUs with AFTR state Date: Fri, 4 Apr 2014 11:48:45 +0200 Message-Id: <1396604925-18383-1-git-send-email-daniel.lezcano@linaro.org> X-Mailer: git-send-email 1.7.9.5 Sender: linux-samsung-soc-owner@vger.kernel.org Precedence: list List-ID: X-Mailing-List: linux-samsung-soc@vger.kernel.org X-Removed-Original-Auth: Dkim didn't pass. X-Original-Sender: daniel.lezcano@linaro.org X-Original-Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.128.169 is neither permitted nor denied by best guess record for domain of patch+caf_=patchwork-forward=linaro.org@linaro.org) smtp.mail=patch+caf_=patchwork-forward=linaro.org@linaro.org Mailing-list: list patchwork-forward@linaro.org; contact patchwork-forward+owners@linaro.org X-Google-Group-Id: 836684582541 List-Post: , List-Help: , List-Archive: List-Unsubscribe: , The following driver is for exynos4210. I did not yet finished the other boards, so I created a specific driver for 4210 which could be merged later. The driver is based on Colin Cross's driver found at: https://android.googlesource.com/kernel/exynos/+/e686b1ec67423c40b4fdf811f9a4dfa3b393a010%5E%5E!/ This one was based on a 3.4 kernel and an old API. It has been refreshed, simplified and based on the recent code cleanup I sent today. The AFTR could be entered when all the cpus (except cpu0) are down. In order to reach this situation, the couple idle states are used. There is a sync barrier at the entry and the exit of the low power function. So all cpus will enter and exit the function at the same time. At this point, CPU0 knows the other cpu will power down itself. CPU0 waits for the CPU1 to be powered down and then initiate the AFTR power down sequence. No interrupts are handled by CPU1, this is why we switch to the timer broadcast even if the local timer is not impacted by the idle state. When CPU0 wakes up, it powers up CPU1 and waits for it to boot. Then they both exit the idle function. This driver allows the exynos4210 to have the same power consumption at idle time than the one when we have to unplug CPU1 in order to let CPU0 to reach the AFTR state. This patch is a RFC because, we have to find a way to remove the macros definitions and cpu powerdown function without pulling the arch dependent headers. Signed-off-by: Daniel Lezcano --- arch/arm/mach-exynos/common.c | 11 +- drivers/cpuidle/Kconfig.arm | 8 ++ drivers/cpuidle/Makefile | 1 + drivers/cpuidle/cpuidle-exynos4210.c | 226 ++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 drivers/cpuidle/cpuidle-exynos4210.c diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index d5fa21e..1765a98 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -299,9 +299,18 @@ static struct platform_device exynos_cpuidle = { .id = -1, }; +static struct platform_device exynos4210_cpuidle = { + .name = "exynos4210-cpuidle", + .dev.platform_data = exynos_sys_powerdown_aftr, + .id = -1, +}; + void __init exynos_cpuidle_init(void) { - platform_device_register(&exynos_cpuidle); + if (soc_is_exynos4210()) + platform_device_register(&exynos4210_cpuidle); + else + platform_device_register(&exynos_cpuidle); } void __init exynos_cpufreq_init(void) diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm index 92f0c12..2772130 100644 --- a/drivers/cpuidle/Kconfig.arm +++ b/drivers/cpuidle/Kconfig.arm @@ -51,3 +51,11 @@ config ARM_EXYNOS_CPUIDLE depends on ARCH_EXYNOS help Select this to enable cpuidle for Exynos processors + +config ARM_EXYNOS4210_CPUIDLE + bool "Cpu Idle Driver for the Exynos 4210 processor" + default y + depends on ARCH_EXYNOS + select ARCH_NEEDS_CPU_IDLE_COUPLED if SMP + help + Select this to enable cpuidle for the Exynos 4210 processors diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 0d1540a..e0ec9bc 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_ARM_ZYNQ_CPUIDLE) += cpuidle-zynq.o obj-$(CONFIG_ARM_U8500_CPUIDLE) += cpuidle-ux500.o obj-$(CONFIG_ARM_AT91_CPUIDLE) += cpuidle-at91.o obj-$(CONFIG_ARM_EXYNOS_CPUIDLE) += cpuidle-exynos.o +obj-$(CONFIG_ARM_EXYNOS4210_CPUIDLE) += cpuidle-exynos4210.o ############################################################################### # POWERPC drivers diff --git a/drivers/cpuidle/cpuidle-exynos4210.c b/drivers/cpuidle/cpuidle-exynos4210.c new file mode 100644 index 0000000..56f6d51 --- /dev/null +++ b/drivers/cpuidle/cpuidle-exynos4210.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Copyright (c) 2014 Linaro : Daniel Lezcano + * http://www.linaro.org + * + * Based on the work of Colin Cross + * + * 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 + +static atomic_t exynos_idle_barrier; +static atomic_t cpu1_wakeup = ATOMIC_INIT(0); + +#define BOOT_VECTOR S5P_VA_SYSRAM +#define S5P_VA_PMU S3C_ADDR(0x02180000) +#define S5P_PMUREG(x) (S5P_VA_PMU + (x)) +#define S5P_ARM_CORE1_CONFIGURATION S5P_PMUREG(0x2080) +#define S5P_ARM_CORE1_STATUS S5P_PMUREG(0x2084) + +static void (*exynos_aftr)(void); + +static int cpu_suspend_finish(unsigned long flags) +{ + if (flags) + exynos_aftr(); + + cpu_do_idle(); + + return -1; +} + +static int exynos_cpu0_enter_aftr(void) +{ + int ret = -1; + + /* + * If the other cpu is powered on, we have to power it off, because + * the AFTR state won't work otherwise + */ + if (cpu_online(1)) { + + /* + * We reach a sync point with the coupled idle state, we know + * the other cpu will power down itself or will abort the + * sequence, let's wait for one of these to happen + */ + while (__raw_readl(S5P_ARM_CORE1_STATUS) & 3) { + + /* + * The other cpu may skip idle and boot back + * up again + */ + if (atomic_read(&cpu1_wakeup)) + goto abort; + + /* + * The other cpu may bounce through idle and + * boot back up again, getting stuck in the + * boot rom code + */ + if (__raw_readl(BOOT_VECTOR) == 0) + goto abort; + + cpu_relax(); + } + } + + cpu_pm_enter(); + + ret = cpu_suspend(1, cpu_suspend_finish); + + cpu_pm_exit(); + +abort: + if (cpu_online(1)) { + /* + * Set the boot vector to something non-zero + */ + __raw_writel(virt_to_phys(s3c_cpu_resume), + BOOT_VECTOR); + dsb(); + + /* + * Turn on cpu1 and wait for it to be on + */ + __raw_writel(0x3, S5P_ARM_CORE1_CONFIGURATION); + while ((__raw_readl(S5P_ARM_CORE1_STATUS) & 3) != 3) + cpu_relax(); + + /* + * Wait for cpu1 to get stuck in the boot rom + */ + while ((__raw_readl(BOOT_VECTOR) != 0) && + !atomic_read(&cpu1_wakeup)) + cpu_relax(); + + if (!atomic_read(&cpu1_wakeup)) { + /* + * Poke cpu1 out of the boot rom + */ + __raw_writel(virt_to_phys(s3c_cpu_resume), + BOOT_VECTOR); + dsb_sev(); + } + + /* + * Wait for cpu1 to finish booting + */ + while (!atomic_read(&cpu1_wakeup)) + cpu_relax(); + } + + return ret; +} + +static int exynos_powerdown_cpu1(void) +{ + int ret = -1; + + /* + * Idle sequence for cpu1 + */ + if (cpu_pm_enter()) + goto cpu1_aborted; + + /* + * Turn off cpu 1 + */ + __raw_writel(0, S5P_ARM_CORE1_CONFIGURATION); + + ret = cpu_suspend(0, cpu_suspend_finish); + + cpu_pm_exit(); + +cpu1_aborted: + dsb(); + /* + * Notify cpu 0 that cpu 1 is awake + */ + atomic_set(&cpu1_wakeup, 1); + + return ret; +} + +static int exynos_enter_aftr(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int ret; + + __raw_writel(virt_to_phys(s3c_cpu_resume), BOOT_VECTOR); + + /* + * Waiting all cpus to reach this point at the same moment + */ + cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); + + /* + * Both cpus will reach this point at the same time + */ + ret = dev->cpu ? exynos_powerdown_cpu1() : exynos_cpu0_enter_aftr(); + if (ret) + index = ret; + + /* + * Waiting all cpus to finish the power sequence before going further + */ + cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); + + atomic_set(&cpu1_wakeup, 0); + + return index; +} + +static struct cpuidle_driver exynos_idle_driver = { + .name = "exynos4210_idle", + .owner = THIS_MODULE, + .states = { + ARM_CPUIDLE_WFI_STATE, + [1] = { + .enter = exynos_enter_aftr, + .exit_latency = 5000, + .target_residency = 10000, + .flags = CPUIDLE_FLAG_TIME_VALID | + CPUIDLE_FLAG_COUPLED | CPUIDLE_FLAG_TIMER_STOP, + .name = "C1", + .desc = "ARM power down", + }, + }, + .state_count = 2, + .safe_state_index = 0, +}; + +static int exynos_cpuidle_probe(struct platform_device *pdev) +{ + exynos_aftr = (void *)(pdev->dev.platform_data); + + return cpuidle_register(&exynos_idle_driver, cpu_possible_mask); +} + +static struct platform_driver exynos_cpuidle_driver = { + .driver = { + .name = "exynos4210-cpuidle", + .owner = THIS_MODULE, + }, + .probe = exynos_cpuidle_probe, +}; + +module_platform_driver(exynos_cpuidle_driver);