From patchwork Fri May 19 20:04:37 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dave Gerlach X-Patchwork-Id: 9738003 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id D8C75601C2 for ; Fri, 19 May 2017 20:06:18 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B284127CAF for ; Fri, 19 May 2017 20:06:18 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A56FE2838B; Fri, 19 May 2017 20:06:18 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2341527CAF for ; Fri, 19 May 2017 20:06:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932380AbdESUFu (ORCPT ); Fri, 19 May 2017 16:05:50 -0400 Received: from lelnx194.ext.ti.com ([198.47.27.80]:20497 "EHLO lelnx194.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753595AbdESUF2 (ORCPT ); Fri, 19 May 2017 16:05:28 -0400 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by lelnx194.ext.ti.com (8.15.1/8.15.1) with ESMTP id v4JK4n63029342; Fri, 19 May 2017 15:04:49 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ti.com; s=ti-com-17Q1; t=1495224289; bh=z7ecoP71fiEJz11xgJsAwnNpJNmynjVIrtyXZtZCDCI=; h=From:To:CC:Subject:Date:In-Reply-To:References; b=eIdy3SrW1XYPXbxcgAtUHaE5338qg4cIppHidp98rNdjJec6CAEQfIZOGjiiCLSQZ JbuEtRB04qb5riSF0BdlajZRFn5p1yR5uC+vu3SVO0dE2viqJXa989uyZFb3CC5xzX BZJzONdHylDCDM28xzsUk9kt25bL7LCGYuMz3OaE= Received: from DLEE70.ent.ti.com (dlemailx.itg.ti.com [157.170.170.113]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id v4JK4nee007400; Fri, 19 May 2017 15:04:49 -0500 Received: from dflp33.itg.ti.com (10.64.6.16) by DLEE70.ent.ti.com (157.170.170.113) with Microsoft SMTP Server id 14.3.294.0; Fri, 19 May 2017 15:04:48 -0500 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 v4JK4mdM024398; Fri, 19 May 2017 15:04:49 -0500 Received: from localhost (uda0274052.dhcp.ti.com [128.247.59.203]) by legion.dal.design.ti.com (8.11.7p1+Sun/8.11.7) with ESMTP id v4JK4m310888; Fri, 19 May 2017 15:04:48 -0500 (CDT) From: Dave Gerlach To: Tony Lindgren , Santosh Shilimkar , Russell King CC: , , , Dave Gerlach , Keerthy J Subject: [PATCH v2 4/5] soc: ti: Add pm33xx driver for basic suspend support Date: Fri, 19 May 2017 15:04:37 -0500 Message-ID: <20170519200438.9502-5-d-gerlach@ti.com> X-Mailer: git-send-email 2.13.0 In-Reply-To: <20170519200438.9502-1-d-gerlach@ti.com> References: <20170519200438.9502-1-d-gerlach@ti.com> MIME-Version: 1.0 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP AM335x and AM437x support various low power modes as documented in section 8.1.4.3 of the AM335x Technical Reference Manual and section 6.4.3 of the AM437x 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. Both platforms have 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 --- drivers/soc/ti/Kconfig | 9 + drivers/soc/ti/Makefile | 1 + drivers/soc/ti/pm33xx.c | 337 +++++++++++++++++++++++++++++++++++ include/linux/platform_data/pm33xx.h | 4 +- 4 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 drivers/soc/ti/pm33xx.c diff --git a/drivers/soc/ti/Kconfig b/drivers/soc/ti/Kconfig index 39e152abe6b9..92770d84a288 100644 --- a/drivers/soc/ti/Kconfig +++ b/drivers/soc/ti/Kconfig @@ -28,6 +28,15 @@ config KEYSTONE_NAVIGATOR_DMA If unsure, say N. +config AMX3_PM + tristate "AMx3 Power Management" + depends on SOC_AM33XX || SOC_AM43XX + depends on WKUP_M3_IPC && TI_EMIF_SRAM && SRAM + help + Enable power management on AM335x and AM437x. Required for suspend to mem + and standby states on both AM335x and AM437x platforms and for deeper cpuidle + c-states on AM335x. + config WKUP_M3_IPC tristate "TI AMx3 Wkup-M3 IPC Driver" depends on WKUP_M3_RPROC diff --git a/drivers/soc/ti/Makefile b/drivers/soc/ti/Makefile index 7d572736c86e..d9bd4ba424be 100644 --- a/drivers/soc/ti/Makefile +++ b/drivers/soc/ti/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_KEYSTONE_NAVIGATOR_QMSS) += knav_qmss.o knav_qmss-y := knav_qmss_queue.o knav_qmss_acc.o obj-$(CONFIG_KEYSTONE_NAVIGATOR_DMA) += knav_dma.o +obj-$(CONFIG_AMX3_PM) += pm33xx.o obj-$(CONFIG_WKUP_M3_IPC) += wkup_m3_ipc.o obj-$(CONFIG_TI_SCI_PM_DOMAINS) += ti_sci_pm_domains.o diff --git a/drivers/soc/ti/pm33xx.c b/drivers/soc/ti/pm33xx.c new file mode 100644 index 000000000000..cfeb8df7e82a --- /dev/null +++ b/drivers/soc/ti/pm33xx.c @@ -0,0 +1,337 @@ +/* + * AM33XX Power Management Routines + * + * Copyright (C) 2012-2017 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 +#include +#include + +#define AMX3_PM_SRAM_SYMBOL_OFFSET(sym) ((unsigned long)(sym) - \ + (unsigned long)pm_sram->do_wfi) + +static int (*am33xx_do_wfi_sram)(unsigned long unused); +static phys_addr_t am33xx_do_wfi_sram_phys; + +static struct gen_pool *sram_pool, *sram_pool_data; +static unsigned long ocmcram_location, ocmcram_location_data; + +static struct am33xx_pm_platform_data *pm_ops; +static struct am33xx_pm_sram_addr *pm_sram; + +static struct wkup_m3_ipc *m3_ipc; + +static u32 sram_suspend_address(unsigned long addr) +{ + return ((unsigned long)am33xx_do_wfi_sram + + AMX3_PM_SRAM_SYMBOL_OFFSET(addr)); +} + +#ifdef CONFIG_SUSPEND +static int am33xx_pm_suspend(suspend_state_t suspend_state) +{ + int i, ret = 0; + + ret = pm_ops->soc_suspend((unsigned long)suspend_state, + am33xx_do_wfi_sram); + + if (ret) { + pr_err("PM: Kernel suspend failure\n"); + } else { + i = m3_ipc->ops->request_pm_status(m3_ipc); + + switch (i) { + case 0: + pr_info("PM: Successfully put all powerdomains to target state\n"); + 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: + case PM_SUSPEND_STANDBY: + ret = am33xx_pm_suspend(suspend_state); + 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 = m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_DEEPSLEEP); + break; + case PM_SUSPEND_STANDBY: + ret = m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_STANDBY); + break; + } + + return ret; +} + +static void am33xx_pm_end(void) +{ + m3_ipc->ops->finish_low_power(m3_ipc); +} + +static int am33xx_pm_valid(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_STANDBY: + 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 */ + +static void am33xx_pm_set_ipc_ops(void) +{ + u32 resume_address; + int temp; + + temp = ti_emif_get_mem_type(); + if (temp < 0) { + pr_err("PM: Cannot determine memory type, no PM available\n"); + return; + } + m3_ipc->ops->set_mem_type(m3_ipc, temp); + + /* Physical resume address to be used by ROM code */ + resume_address = am33xx_do_wfi_sram_phys + + *pm_sram->resume_offset + 0x4; + + m3_ipc->ops->set_resume_address(m3_ipc, (void *)resume_address); +} + +static void am33xx_pm_free_sram(void) +{ + gen_pool_free(sram_pool, ocmcram_location, *pm_sram->do_wfi_sz); + gen_pool_free(sram_pool_data, ocmcram_location_data, + sizeof(struct am33xx_pm_ro_sram_data)); +} + +/* + * Push the minimal suspend-resume code to SRAM + */ +static int am33xx_prepare_push_sram_idle(void) +{ + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "ti,omap3-mpu"); + + if (!np) { + np = of_find_compatible_node(NULL, NULL, "ti,omap4-mpu"); + if (!np) { + pr_warn("PM: %s: Unable to find device node for mpu\n", + __func__); + return -ENODEV; + } + } + + sram_pool = of_gen_pool_get(np, "pm-sram", 0); + if (!sram_pool) { + pr_warn("PM: %s: Unable to get sram pool for ocmcram\n", + __func__); + return -ENODEV; + } + + sram_pool_data = of_gen_pool_get(np, "pm-sram", 1); + if (!sram_pool_data) { + pr_warn("PM: %s: Unable to get sram data pool for ocmcram\n", + __func__); + return -ENODEV; + } + + ocmcram_location = gen_pool_alloc(sram_pool, *pm_sram->do_wfi_sz); + if (!ocmcram_location) { + pr_warn("PM: %s: Unable to allocate memory from ocmcram\n", + __func__); + return -ENOMEM; + } + + ocmcram_location_data = gen_pool_alloc(sram_pool_data, + sizeof(struct emif_regs_amx3)); + if (!ocmcram_location_data) { + pr_err("PM: Unable to allocate memory from ocmcram\n"); + gen_pool_free(sram_pool, ocmcram_location, *pm_sram->do_wfi_sz); + return -ENOMEM; + } + + return 0; +} + +static int am33xx_push_sram_idle(void) +{ + struct am33xx_pm_ro_sram_data ro_sram_data; + int ret; + void *copy_addr; + + ro_sram_data.amx3_pm_sram_data_virt = ocmcram_location_data; + ro_sram_data.amx3_pm_sram_data_phys = + gen_pool_virt_to_phys(sram_pool_data, ocmcram_location_data); + + /* 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 = sram_exec_copy(sram_pool, (void *)ocmcram_location, + pm_sram->do_wfi, + *pm_sram->do_wfi_sz); + if (!am33xx_do_wfi_sram) { + pr_err("PM: %s: am33xx_do_wfi copy to sram failed\n", __func__); + return -ENODEV; + } + + ret = ti_emif_copy_pm_function_table(sram_pool, + (void *)sram_suspend_address((unsigned long)pm_sram->emif_sram_table)); + if (ret) { + pr_warn("PM: %s: EMIF function copy failed\n", __func__); + return -EPROBE_DEFER; + } + + copy_addr = sram_exec_copy(sram_pool, + (void *)sram_suspend_address((unsigned long)pm_sram->ro_sram_data), + &ro_sram_data, + sizeof(ro_sram_data)); + if (!copy_addr) { + pr_err("PM: %s: ro_sram_data copy to sram failed\n", __func__); + return -ENODEV; + } + + return 0; +} + +static int am33xx_pm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + + if (!of_machine_is_compatible("ti,am33xx") && + !of_machine_is_compatible("ti,am43")) + return -ENODEV; + + pm_ops = dev->platform_data; + if (!pm_ops) { + pr_err("PM: Cannot get core PM ops!\n"); + return -ENODEV; + } + + pm_sram = pm_ops->get_sram_addrs(); + if (!pm_sram) { + pr_err("PM: Cannot get PM asm function addresses!!\n"); + return -ENODEV; + } + + ret = am33xx_prepare_push_sram_idle(); + if (ret) + return ret; + + ret = am33xx_push_sram_idle(); + if (ret) + goto err_free_sram; + + m3_ipc = wkup_m3_ipc_get(); + if (!m3_ipc) { + pr_err("PM: Cannot get wkup_m3_ipc handle\n"); + ret = -EPROBE_DEFER; + goto err_free_sram; + } + + am33xx_pm_set_ipc_ops(); + +#ifdef CONFIG_SUSPEND + suspend_set_ops(&am33xx_pm_ops); +#endif /* CONFIG_SUSPEND */ + + ret = pm_ops->init(); + if (ret) { + pr_err("Unable to call core pm init!\n"); + ret = -ENODEV; + goto err_put_wkup_m3_ipc; + } + + return 0; + +err_put_wkup_m3_ipc: + wkup_m3_ipc_put(m3_ipc); +err_free_sram: + am33xx_pm_free_sram(); + return ret; +} + +static int am33xx_pm_remove(struct platform_device *pdev) +{ + suspend_set_ops(NULL); + wkup_m3_ipc_put(m3_ipc); + am33xx_pm_free_sram(); + return 0; +} + +static struct platform_driver am33xx_pm_driver = { + .driver = { + .name = "pm33xx", + }, + .probe = am33xx_pm_probe, + .remove = am33xx_pm_remove, +}; +module_platform_driver(am33xx_pm_driver); + +MODULE_ALIAS("platform:pm33xx"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("am33xx power management driver"); diff --git a/include/linux/platform_data/pm33xx.h b/include/linux/platform_data/pm33xx.h index c191ab681093..b8d6c3a9b46c 100644 --- a/include/linux/platform_data/pm33xx.h +++ b/include/linux/platform_data/pm33xx.h @@ -38,12 +38,12 @@ struct am33xx_pm_sram_data { u32 wfi_flags; u32 l2_aux_ctrl_val; u32 l2_prefetch_ctrl_val; -}; +} __packed __aligned(8); struct am33xx_pm_ro_sram_data { u32 amx3_pm_sram_data_virt; u32 amx3_pm_sram_data_phys; -}; +} __packed __aligned(8); extern inline void amx3_pm_asm_offsets(void) {