From patchwork Wed Nov 26 21:52:32 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dave Gerlach X-Patchwork-Id: 5388731 Return-Path: X-Original-To: patchwork-linux-omap@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 9D69AC11AC for ; Wed, 26 Nov 2014 21:53:26 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 66CDD201EF for ; Wed, 26 Nov 2014 21:53:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id E2F8B201FA for ; Wed, 26 Nov 2014 21:53:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753574AbaKZVxT (ORCPT ); Wed, 26 Nov 2014 16:53:19 -0500 Received: from comal.ext.ti.com ([198.47.26.152]:59785 "EHLO comal.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753284AbaKZVxR (ORCPT ); Wed, 26 Nov 2014 16:53:17 -0500 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by comal.ext.ti.com (8.13.7/8.13.7) with ESMTP id sAQLqqd0004409; Wed, 26 Nov 2014 15:52:52 -0600 Received: from DLEE71.ent.ti.com (dlee71.ent.ti.com [157.170.170.114]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id sAQLqqiD007459; Wed, 26 Nov 2014 15:52:52 -0600 Received: from dflp33.itg.ti.com (10.64.6.16) by DLEE71.ent.ti.com (157.170.170.114) with Microsoft SMTP Server id 14.3.174.1; Wed, 26 Nov 2014 15:52:52 -0600 Received: from legion.dal.design.ti.com (legion.dal.design.ti.com [128.247.22.53]) by dflp33.itg.ti.com (8.14.3/8.13.8) with ESMTP id sAQLqpjS008769; Wed, 26 Nov 2014 15:52:52 -0600 Received: from localhost (j-172-22-146-2.vpn.ti.com [172.22.146.2]) by legion.dal.design.ti.com (8.11.7p1+Sun/8.11.7) with ESMTP id sAQLqpt16855; Wed, 26 Nov 2014 15:52:51 -0600 (CST) From: Dave Gerlach To: , , CC: Tony Lindgren , Benoit Cousson , Paul Walmsley , Kevin Hilman , Dave Gerlach Subject: [RFC PATCH v5 3/4] ARM: OMAP2+: AM33XX: Basic suspend resume support Date: Wed, 26 Nov 2014 15:52:32 -0600 Message-ID: X-Mailer: git-send-email 2.1.0 In-Reply-To: References: MIME-Version: 1.0 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, 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 AM335x supports various low power modes as documented in section 8.1.4.3 of the AM335x Technical Reference Manual. DeepSleep0 mode offers the lowest power mode with limited wakeup sources without a system reboot and is mapped as the suspend state in the kernel. In this state, MPU and PER domains are turned off with the internal RAM held in retention to facilitate the resume process. As part of the boot process, the assembly code is copied over to OCMCRAM so it can be executed to turn of the EMIF and put DDR into self refresh. AM335x has a Cortex-M3 (WKUP_M3) which assists the MPU in DeepSleep0 entry and exit. WKUP_M3 takes care of the clockdomain and powerdomain transitions based on the intended low power state. MPU needs to load the appropriate WKUP_M3 binary onto the WKUP_M3 memory space before it can leverage any of the PM features like DeepSleep. This loading is handled by the remoteproc driver wkup_m3_rproc. Communication with the WKUP_M3 is handled by a wkup_m3_ipc driver that exposes the specific PM functionality to be used the PM code. In the current implementation when the suspend process is initiated, MPU interrupts the WKUP_M3 to let it know about the intent of entering DeepSleep0 and waits for an ACK. When the ACK is received MPU continues with its suspend process to suspend all the drivers and then jumps to assembly in OCMC RAM. The assembly code puts the external RAM in self-refresh mode, gates the MPU clock, and then finally executes the WFI instruction. Execution of the WFI instruction with MPU clock gated triggers another interrupt to the WKUP_M3 which then continues with the power down sequence wherein the clockdomain and powerdomain transition takes place. As part of the sleep sequence, WKUP_M3 unmasks the interrupt lines for the wakeup sources. WFI execution on WKUP_M3 causes the hardware to disable the main oscillator of the SoC and from here system remains in sleep state until a wake source brings the system into resume path. When a wakeup event occurs, WKUP_M3 starts the power-up sequence by switching on the power domains and finally enabling the clock to MPU. Since the MPU gets powered down as part of the sleep sequence in the resume path ROM code starts executing. The ROM code detects a wakeup from sleep and then jumps to the resume location in OCMC which was populated in one of the IPC registers as part of the suspend sequence. Code is based on work by Vaibhav Bedia. Signed-off-by: Dave Gerlach --- arch/arm/boot/dts/am33xx.dtsi | 2 + arch/arm/mach-omap2/pm33xx.c | 250 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 arch/arm/mach-omap2/pm33xx.c diff --git a/arch/arm/boot/dts/am33xx.dtsi b/arch/arm/boot/dts/am33xx.dtsi index b432499..c4210d2 100644 --- a/arch/arm/boot/dts/am33xx.dtsi +++ b/arch/arm/boot/dts/am33xx.dtsi @@ -80,6 +80,7 @@ mpu { compatible = "ti,omap3-mpu"; ti,hwmods = "mpu"; + sram = <&ocmcram>; }; }; @@ -744,6 +745,7 @@ ocmcram: ocmcram@40300000 { compatible = "mmio-sram"; reg = <0x40300000 0x10000>; /* 64k */ + map-exec; }; wkup_m3: wkup_m3@44d00000 { diff --git a/arch/arm/mach-omap2/pm33xx.c b/arch/arm/mach-omap2/pm33xx.c new file mode 100644 index 0000000..2baf810 --- /dev/null +++ b/arch/arm/mach-omap2/pm33xx.c @@ -0,0 +1,250 @@ +/* + * AM33XX Power Management Routines + * + * Copyright (C) 2012-2014 Texas Instruments Incorporated - http://www.ti.com/ + * Vaibhav Bedia, Dave Gerlach + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "clockdomain.h" +#include "cm33xx.h" +#include "common.h" +#include "pm.h" +#include "powerdomain.h" +#include "soc.h" + +static struct powerdomain *cefuse_pwrdm, *gfx_pwrdm, *per_pwrdm, *mpu_pwrdm; +static struct clockdomain *gfx_l4ls_clkdm; + +static int (*am33xx_do_wfi_sram)(unsigned long unused); +static phys_addr_t am33xx_do_wfi_sram_phys; + +#ifdef CONFIG_SUSPEND +static int am33xx_pm_suspend(void) +{ + int i, ret = 0; + int status = 0; + + /* Try to put GFX to sleep */ + omap_set_pwrdm_state(gfx_pwrdm, PWRDM_POWER_OFF); + + ret = cpu_suspend(0, am33xx_do_wfi_sram); + + status = pwrdm_read_pwrst(gfx_pwrdm); + if (status != PWRDM_POWER_OFF) + pr_err("PM: GFX domain did not transition\n"); + + /* + * BUG: GFX_L4LS clock domain needs to be woken up to + * ensure thet L4LS clock domain does not get stuck in transition + * If that happens L3 module does not get disabled, thereby leading + * to PER power domain transition failing + */ + clkdm_wakeup(gfx_l4ls_clkdm); + clkdm_sleep(gfx_l4ls_clkdm); + + if (ret) { + pr_err("PM: Kernel suspend failure\n"); + } else { + i = wkup_m3_request_pm_status(); + + switch (i) { + case 0: + pr_info("PM: Successfully put all powerdomains to target state\n"); + + /* + * The PRCM registers on AM335x do not contain + * previous state information like those present on + * OMAP4 so we must manually indicate transition so + * state counters are properly incremented + */ + pwrdm_post_transition(mpu_pwrdm); + pwrdm_post_transition(per_pwrdm); + break; + case 1: + pr_err("PM: Could not transition all powerdomains to target state\n"); + ret = -1; + break; + default: + pr_err("PM: CM3 returned unknown result = %d\n", i); + ret = -1; + } + } + + return ret; +} + +static int am33xx_pm_enter(suspend_state_t suspend_state) +{ + int ret = 0; + + switch (suspend_state) { + case PM_SUSPEND_MEM: + ret = am33xx_pm_suspend(); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int am33xx_pm_begin(suspend_state_t state) +{ + int ret = -EINVAL; + + switch (state) { + case PM_SUSPEND_MEM: + ret = wkup_m3_prepare_low_power(state); + break; + } + + return ret; +} + +static void am33xx_pm_end(void) +{ + wkup_m3_finish_low_power(); +} + +static int am33xx_pm_valid(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_MEM: + return 1; + default: + return 0; + } +} + +static const struct platform_suspend_ops am33xx_pm_ops = { + .begin = am33xx_pm_begin, + .end = am33xx_pm_end, + .enter = am33xx_pm_enter, + .valid = am33xx_pm_valid, +}; +#endif /* CONFIG_SUSPEND */ + +/* + * Push the minimal suspend-resume code to SRAM + */ +static int am33xx_push_sram_idle(void) +{ + struct device_node *np; + struct gen_pool *sram_pool; + phys_addr_t ocmcram_location; + int ret; + + ret = ti_emif_copy_pm_function_table(&am33xx_emif_sram_table); + if (ret) { + pr_err("PM: Cannot copy emif functions to sram, no PM available\n"); + return -ENODEV; + } + + np = of_find_compatible_node(NULL, NULL, "ti,omap3-mpu"); + + if (!np) { + pr_warn("PM: %s: Unable to find device node for mpu\n", + __func__); + return -ENODEV; + } + + sram_pool = of_get_named_gen_pool(np, "sram", 0); + + if (!sram_pool) { + pr_warn("PM: %s: Unable to allocate sram pool for ocmcram\n", + __func__); + return -ENODEV; + } + + ocmcram_location = gen_pool_alloc(sram_pool, am33xx_do_wfi_sz); + if (!ocmcram_location) + return -EINVAL; + + /* Save physical address to calculate resume offset during pm init */ + am33xx_do_wfi_sram_phys = gen_pool_virt_to_phys(sram_pool, + ocmcram_location); + am33xx_do_wfi_sram = (void *)fncpy((void *)ocmcram_location, + &am33xx_do_wfi, + am33xx_do_wfi_sz); + + return 0; +} + +int __init am33xx_pm_init(void) +{ + int ret; + void *resume_address; + u32 temp; + + if (!soc_is_am33xx()) + return -ENODEV; + + gfx_pwrdm = pwrdm_lookup("gfx_pwrdm"); + per_pwrdm = pwrdm_lookup("per_pwrdm"); + mpu_pwrdm = pwrdm_lookup("mpu_pwrdm"); + + gfx_l4ls_clkdm = clkdm_lookup("gfx_l4ls_gfx_clkdm"); + + if ((!gfx_pwrdm) || (!per_pwrdm) || (!mpu_pwrdm) || (!gfx_l4ls_clkdm)) { + pr_err("PM: Could not lookup clock and power domains\n"); + return -ENODEV; + } + + (void)clkdm_for_each(omap_pm_clkdms_setup, NULL); + + /* CEFUSE domain can be turned off post bootup */ + cefuse_pwrdm = pwrdm_lookup("cefuse_pwrdm"); + if (cefuse_pwrdm) + omap_set_pwrdm_state(cefuse_pwrdm, PWRDM_POWER_OFF); + else + pr_warn("PM: Failed to get cefuse_pwrdm\n"); + + ret = am33xx_push_sram_idle(); + if (ret) + return ret; + + /* Physical resume address to be used by ROM code */ + resume_address = (void *)am33xx_do_wfi_sram_phys + + am33xx_resume_offset + 0x4; + + wkup_m3_set_resume_address(resume_address); + + temp = ti_emif_get_mem_type(); + if (temp < 0) { + pr_err("PM: Cannot determine memory type, no PM available\n"); + return -ENODEV; + } + wkup_m3_set_mem_type(temp); + +#ifdef CONFIG_SUSPEND + suspend_set_ops(&am33xx_pm_ops); +#endif /* CONFIG_SUSPEND */ + + return 0; +}