From patchwork Tue Aug 19 22:15:32 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lina Iyer X-Patchwork-Id: 35675 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-ie0-f198.google.com (mail-ie0-f198.google.com [209.85.223.198]) by ip-10-151-82-157.ec2.internal (Postfix) with ESMTPS id 9D89C2034C for ; Tue, 19 Aug 2014 22:16:07 +0000 (UTC) Received: by mail-ie0-f198.google.com with SMTP id rl12sf7543291iec.9 for ; Tue, 19 Aug 2014 15:16:07 -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:in-reply-to:references:sender:precedence:list-id :x-original-sender:x-original-authentication-results:mailing-list :list-post:list-help:list-archive:list-unsubscribe; bh=I1cM/GsJYKmEnGGQ3yQ8YyXUsx4bPx9/4O2Qfa5lmJM=; b=gg7GPKwwIG9oyb0nK0hhf9+JtQ/EewA6WgpMrmfv9Ul8XIyUh20cea2dPIXzdlha8m Zjk8VxmHwVPMFFpyh5j8Dmg5qXWDObjWJTSya1mBcRh1pzKHC850+qAMGVGYSDqPdEqi xZYDXI64Pk8jzKwpeAJAFdQaJh3mRTW7j8F//BNZg+LCmEd0k2EbZrv35hJbPG/lLGc1 UiL408usaIvWFdfnd4xRfws64W+NxQa/rcSvzqgGJ+Ik5OJFd9NWXLmU8A1jez6ffk/P KOmCR4ILyrOn2CdAzrwQxurykLVfkDd5zypLjSHaZj+le0Wbbgb4ppNMjuLt3NCV1WDd iWcg== X-Gm-Message-State: ALoCoQmGd4DcgNB1hqdtD8G5oT2NsYHThNLBUD6FODu6Ew7OE5b6fHJTC2pIHDE49PNP0+k0Khoz X-Received: by 10.43.1.133 with SMTP id nq5mr24701340icb.21.1408486567181; Tue, 19 Aug 2014 15:16:07 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: patchwork-forward@linaro.org Received: by 10.140.109.117 with SMTP id k108ls42470qgf.3.gmail; Tue, 19 Aug 2014 15:16:07 -0700 (PDT) X-Received: by 10.52.26.173 with SMTP id m13mr2504114vdg.69.1408486567063; Tue, 19 Aug 2014 15:16:07 -0700 (PDT) Received: from mail-vc0-f178.google.com (mail-vc0-f178.google.com [209.85.220.178]) by mx.google.com with ESMTPS id hi3si9671479vdb.101.2014.08.19.15.16.07 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Tue, 19 Aug 2014 15:16:07 -0700 (PDT) Received-SPF: pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.220.178 as permitted sender) client-ip=209.85.220.178; Received: by mail-vc0-f178.google.com with SMTP id la4so8117542vcb.23 for ; Tue, 19 Aug 2014 15:16:07 -0700 (PDT) X-Received: by 10.52.119.229 with SMTP id kx5mr3521418vdb.40.1408486566959; Tue, 19 Aug 2014 15:16:06 -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.221.45.67 with SMTP id uj3csp3354vcb; Tue, 19 Aug 2014 15:16:06 -0700 (PDT) X-Received: by 10.68.95.196 with SMTP id dm4mr47848660pbb.95.1408486565119; Tue, 19 Aug 2014 15:16:05 -0700 (PDT) Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id df3si28839804pbc.99.2014.08.19.15.16.04 for ; Tue, 19 Aug 2014 15:16:05 -0700 (PDT) Received-SPF: none (google.com: linux-arm-msm-owner@vger.kernel.org does not designate permitted sender hosts) client-ip=209.132.180.67; Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751710AbaHSWQD (ORCPT + 5 others); Tue, 19 Aug 2014 18:16:03 -0400 Received: from mail-pa0-f51.google.com ([209.85.220.51]:36339 "EHLO mail-pa0-f51.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751747AbaHSWQC (ORCPT ); Tue, 19 Aug 2014 18:16:02 -0400 Received: by mail-pa0-f51.google.com with SMTP id ey11so10593804pad.24 for ; Tue, 19 Aug 2014 15:15:59 -0700 (PDT) X-Received: by 10.68.232.163 with SMTP id tp3mr47902705pbc.97.1408486558938; Tue, 19 Aug 2014 15:15:58 -0700 (PDT) Received: from ubuntu.localdomain (proxy6-global253.qualcomm.com. [199.106.103.253]) by mx.google.com with ESMTPSA id zq5sm20305897pbb.37.2014.08.19.15.15.57 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 19 Aug 2014 15:15:58 -0700 (PDT) From: Lina Iyer To: daniel.lezcano@linaro.org, khilman@linaro.org, sboyd@codeaurora.org, davidb@codeaurora.org, galak@codeaurora.org, linux-arm-msm@vger.kernel.org, lorenzo.pieralisi@arm.com Cc: msivasub@codeaurora.org, Lina Iyer , Praveen Chidambaram Subject: [PATCH v4 3/8] qcom: spm: Add Subsystem Power Manager driver (SAW2) Date: Tue, 19 Aug 2014 16:15:32 -0600 Message-Id: <1408486537-6358-4-git-send-email-lina.iyer@linaro.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1408486537-6358-1-git-send-email-lina.iyer@linaro.org> References: <1408486537-6358-1-git-send-email-lina.iyer@linaro.org> Sender: linux-arm-msm-owner@vger.kernel.org Precedence: list List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org X-Removed-Original-Auth: Dkim didn't pass. X-Original-Sender: lina.iyer@linaro.org X-Original-Authentication-Results: mx.google.com; spf=pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.220.178 as permitted sender) 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: , SPM is a hardware block that controls the peripheral logic surrounding the application cores (cpu/l$). When the core executes WFI instruction, the SPM takes over the putting the core in low power state as configured. The wake up for the SPM is an interrupt at the GIC, which then completes the rest of low power mode sequence and brings the core out of low power mode. The SPM has a set of control registers that configure the SPMs individually based on the type of the core and the runtime conditions. SPM is a finite state machine block to which a sequence is provided and it interprets the bytes and executes them in sequence. Each low power mode that the core can enter into is provided to the SPM as a sequence. Configure the SPM to set the core (cpu or L2) into its low power mode, the index of the first command in the sequence is set in the SPM_CTL register. When the core executes ARM wfi instruction, it triggers the SPM state machine to start executing from that index. The SPM state machine waits until the interrupt occurs and starts executing the rest of the sequence until it hits the end of the sequence. The end of the sequence jumps the core out of its low power mode. Signed-off-by: Praveen Chidambaram Signed-off-by: Lina Iyer --- drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/spm-drv.h | 71 +++++++++++++++++ drivers/soc/qcom/spm.c | 195 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 drivers/soc/qcom/spm-drv.h create mode 100644 drivers/soc/qcom/spm.c diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 70d52ed..20b329f 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o +obj-$(CONFIG_QCOM_PM) += spm.o CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o diff --git a/drivers/soc/qcom/spm-drv.h b/drivers/soc/qcom/spm-drv.h new file mode 100644 index 0000000..4f41f1b --- /dev/null +++ b/drivers/soc/qcom/spm-drv.h @@ -0,0 +1,71 @@ +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __QCOM_SPM_DRIVER_H +#define __QCOM_SPM_DRIVER_H + +enum { + MSM_SPM_REG_SAW2_CFG, + MSM_SPM_REG_SAW2_AVS_CTL, + MSM_SPM_REG_SAW2_AVS_HYSTERESIS, + MSM_SPM_REG_SAW2_SPM_CTL, + MSM_SPM_REG_SAW2_PMIC_DLY, + MSM_SPM_REG_SAW2_AVS_LIMIT, + MSM_SPM_REG_SAW2_AVS_DLY, + MSM_SPM_REG_SAW2_SPM_DLY, + MSM_SPM_REG_SAW2_PMIC_DATA_0, + MSM_SPM_REG_SAW2_PMIC_DATA_1, + MSM_SPM_REG_SAW2_PMIC_DATA_2, + MSM_SPM_REG_SAW2_PMIC_DATA_3, + MSM_SPM_REG_SAW2_PMIC_DATA_4, + MSM_SPM_REG_SAW2_PMIC_DATA_5, + MSM_SPM_REG_SAW2_PMIC_DATA_6, + MSM_SPM_REG_SAW2_PMIC_DATA_7, + MSM_SPM_REG_SAW2_RST, + + MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST, + + MSM_SPM_REG_SAW2_ID, + MSM_SPM_REG_SAW2_SECURE, + MSM_SPM_REG_SAW2_STS0, + MSM_SPM_REG_SAW2_STS1, + MSM_SPM_REG_SAW2_STS2, + MSM_SPM_REG_SAW2_VCTL, + MSM_SPM_REG_SAW2_SEQ_ENTRY, + MSM_SPM_REG_SAW2_SPM_STS, + MSM_SPM_REG_SAW2_AVS_STS, + MSM_SPM_REG_SAW2_PMIC_STS, + MSM_SPM_REG_SAW2_VERSION, + + MSM_SPM_REG_NR, +}; + +struct msm_spm_mode { + uint32_t mode; + uint8_t *cmd; + uint32_t start_addr; +}; + +struct msm_spm_driver_data { + void __iomem *reg_base_addr; + uint32_t reg_shadow[MSM_SPM_REG_NR]; + uint32_t *reg_offsets; + struct msm_spm_mode *modes; + uint32_t num_modes; +}; + +int msm_spm_drv_init(struct msm_spm_driver_data *dev); +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, + uint32_t addr); +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev, + bool enable); + +#endif /* __QCOM_SPM_DRIVER_H */ diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c new file mode 100644 index 0000000..19b79d0 --- /dev/null +++ b/drivers/soc/qcom/spm.c @@ -0,0 +1,195 @@ +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 "spm-drv.h" + +#define NUM_SEQ_ENTRY 32 + +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = { + [MSM_SPM_REG_SAW2_SECURE] = 0x00, + [MSM_SPM_REG_SAW2_ID] = 0x04, + [MSM_SPM_REG_SAW2_CFG] = 0x08, + [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, + [MSM_SPM_REG_SAW2_RST] = 0x18, + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, +}; + +static void flush_shadow(struct msm_spm_driver_data *drv, + unsigned int reg_index) +{ + __raw_writel(drv->reg_shadow[reg_index], + drv->reg_base_addr + drv->reg_offsets[reg_index]); +} + +static void load_shadow(struct msm_spm_driver_data *drv, + unsigned int reg_index) +{ + drv->reg_shadow[reg_index] = __raw_readl(drv->reg_base_addr + + drv->reg_offsets[reg_index]); +} + +static inline void set_start_addr(struct msm_spm_driver_data *drv, + uint32_t addr) +{ + /* Update bits 10:4 in the SPM CTL register */ + addr &= 0x7F; + addr <<= 4; + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; +} + +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *drv, + uint32_t mode) +{ + int i; + uint32_t start_addr = 0; + + for (i = 0; i < drv->num_modes; i++) { + if (drv->modes[i].mode == mode) { + start_addr = drv->modes[i].start_addr; + break; + } + } + + if (i == drv->num_modes) + return -EINVAL; + + set_start_addr(drv, start_addr); + flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL); + /* Barrier to ensure we have written the start address */ + wmb(); + + /* Update our shadow with the status changes, if any */ + load_shadow(drv, MSM_SPM_REG_SAW2_SPM_STS); + + return 0; +} + +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *drv, bool enable) +{ + uint32_t value = enable ? 0x01 : 0x00; + + /* Update BIT(0) of SPM_CTL to enable/disable the SPM */ + if ((drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { + /* Clear the existing value and update */ + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; + flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL); + /* Ensure we have enabled/disabled before returning */ + wmb(); + } + + return 0; +} + +static void flush_seq_data(struct msm_spm_driver_data *drv, + uint32_t *reg_seq_entry) +{ + int i; + + /* Write the 32 byte array into the SPM registers */ + for (i = 0; i < NUM_SEQ_ENTRY; i++) { + __raw_writel(reg_seq_entry[i], + drv->reg_base_addr + + drv->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] + + 4 * i); + } + /* Ensure that the changes are written */ + wmb(); +} + +static void write_seq_data(struct msm_spm_driver_data *drv, + uint32_t *reg_seq_entry, uint8_t *cmd, uint32_t *offset) +{ + uint32_t cmd_w; + uint32_t offset_w = *offset / 4; + uint8_t last_cmd; + + while (1) { + int i; + + cmd_w = 0; + last_cmd = 0; + cmd_w = reg_seq_entry[offset_w]; + + for (i = (*offset % 4); i < 4; i++) { + last_cmd = *(cmd++); + cmd_w |= last_cmd << (i * 8); + (*offset)++; + if (last_cmd == 0x0f) + break; + } + + reg_seq_entry[offset_w++] = cmd_w; + if (last_cmd == 0x0f) + break; + } + +} + +int msm_spm_drv_init(struct msm_spm_driver_data *drv) +{ + int i; + int offset = 0; + uint32_t sequences[NUM_SEQ_ENTRY/sizeof(uint8_t)] = {0}; + + drv->reg_offsets = msm_spm_reg_offsets_saw2_v2_1; + + /** + * Compose the uint32 array based on the individual bytes of the SPM + * sequence for each low power mode that we read from the DT. + * The sequences are appended if there is space available in the + * integer after the end of the previous sequence. + */ + for (i = 0; i < drv->num_modes; i++) { + drv->modes[i].start_addr = offset; + write_seq_data(drv, &sequences[0], drv->modes[i].cmd, &offset); + } + + /* Flush the integer array */ + flush_seq_data(drv, &sequences[0]); + + /** + * Initialize the hardware with the control registers that + * we have read. + */ + for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0; i++) + flush_shadow(drv, i); + + return 0; +}