From patchwork Tue Aug 12 19:43:53 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lina Iyer X-Patchwork-Id: 35312 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-qc0-f198.google.com (mail-qc0-f198.google.com [209.85.216.198]) by ip-10-151-82-157.ec2.internal (Postfix) with ESMTPS id AFDBC20540 for ; Tue, 12 Aug 2014 19:44:23 +0000 (UTC) Received: by mail-qc0-f198.google.com with SMTP id r5sf11914111qcx.9 for ; Tue, 12 Aug 2014 12:44:23 -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=J0nc8uWd3Sb878NCR88FXGWseHQULreIQ1WAQXn64lk=; b=GRhYpjDikU+1pg3lDGAYjOW76buqaNezt0sij7L7SrnpRgdR2EJBB5wsHO3U3MGROY pZExYzSyRjXTTvoE2/d47YTldfuRWkcCfh+Hehc1k7zvrkoyQfAojZsHajD/9ZxrzqHq WBAyYEhfAzovmlU4JlLzwENlFjVfuWsDThDwVzCciM5oCw8Vf0zF712Hwd3jzji1Usp3 9xHy2lyvgw1uR8luj7Qgh3cUVfHYM1WBr3qg3bMhrSkmpY7kARfU0gKHKcQI/KmOIojS 6JcYAdom6BqDavb7tiQRnHq/KD+sW95zHWoM4TrFZrh3ZVQzhsuuVv2mv4hrcOkDHrvV wD4A== X-Gm-Message-State: ALoCoQn/iItCx9wcCvEWjD1+eUKAeF0DIGvtToXmdpqQ4EKz6vzinWbD8wkTZBJdZ4zPSHocNgfS X-Received: by 10.236.117.141 with SMTP id j13mr18151845yhh.3.1407872663558; Tue, 12 Aug 2014 12:44:23 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: patchwork-forward@linaro.org Received: by 10.140.83.209 with SMTP id j75ls275464qgd.80.gmail; Tue, 12 Aug 2014 12:44:23 -0700 (PDT) X-Received: by 10.220.105.142 with SMTP id t14mr1986016vco.14.1407872663454; Tue, 12 Aug 2014 12:44:23 -0700 (PDT) Received: from mail-vc0-f171.google.com (mail-vc0-f171.google.com [209.85.220.171]) by mx.google.com with ESMTPS id fg6si8805394vcb.46.2014.08.12.12.44.23 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Tue, 12 Aug 2014 12:44:23 -0700 (PDT) Received-SPF: pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.220.171 as permitted sender) client-ip=209.85.220.171; Received: by mail-vc0-f171.google.com with SMTP id hq11so13926093vcb.30 for ; Tue, 12 Aug 2014 12:44:23 -0700 (PDT) X-Received: by 10.52.3.40 with SMTP id 8mr25357919vdz.24.1407872663193; Tue, 12 Aug 2014 12:44:23 -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.37.5 with SMTP id tc5csp274412vcb; Tue, 12 Aug 2014 12:44:21 -0700 (PDT) X-Received: by 10.68.220.34 with SMTP id pt2mr5969185pbc.47.1407872660988; Tue, 12 Aug 2014 12:44:20 -0700 (PDT) Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id rt17si16761455pac.146.2014.08.12.12.44.20 for ; Tue, 12 Aug 2014 12:44:20 -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 S1755084AbaHLToT (ORCPT + 4 others); Tue, 12 Aug 2014 15:44:19 -0400 Received: from mail-pd0-f178.google.com ([209.85.192.178]:57241 "EHLO mail-pd0-f178.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754999AbaHLToR (ORCPT ); Tue, 12 Aug 2014 15:44:17 -0400 Received: by mail-pd0-f178.google.com with SMTP id w10so13115694pde.37 for ; Tue, 12 Aug 2014 12:44:16 -0700 (PDT) X-Received: by 10.68.69.109 with SMTP id d13mr6133799pbu.111.1407872656376; Tue, 12 Aug 2014 12:44:16 -0700 (PDT) Received: from ubuntu.localdomain (proxy6-global253.qualcomm.com. [199.106.103.253]) by mx.google.com with ESMTPSA id n10sm23474696pdp.72.2014.08.12.12.44.14 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 12 Aug 2014 12:44:15 -0700 (PDT) From: Lina Iyer To: daniel.lezcano@linaro.org, khilman@linaro.org, amit.kucheria@linaro.org, sboyd@codeaurora.org, davidb@codeaurora.org, galak@codeaurora.org, linux-arm-msm@vger.kernel.org Cc: msivasub@codeaurora.org, Lina Iyer , Praveen Chidamabram , Murali Nalajala Subject: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Date: Tue, 12 Aug 2014 13:43:53 -0600 Message-Id: <1407872640-6732-4-git-send-email-lina.iyer@linaro.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1407872640-6732-1-git-send-email-lina.iyer@linaro.org> References: <1407872640-6732-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.171 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: , Qualcomm chipsets use an separate h/w block to control the logic around the processor cores (cpu and L2). The SPM h/w block regulates power to the cores and controls the power when the core enter low power modes. Each core has its own instance of SPM. The SPM has the following key functions - Configure the h/w dependencies when entering low power modes - Wait for interrupt and wake up on interrupt - Ensure the dependencies are ready before bringing the core out of sleep - Regulating voltage to the core, interfacing with the PMIC. - Optimize power based on runtime recommendations. The driver identifies and configures the SPMs, by reading the nodes and the register values from the devicetree. The SPMs need to be configured to allow the processor to be idled in a low power state. Signed-off-by: Praveen Chidamabram Signed-off-by: Murali Nalajala Signed-off-by: Lina Iyer --- .../devicetree/bindings/arm/msm/spm-v2.txt | 62 ++ drivers/soc/qcom/Makefile | 2 + drivers/soc/qcom/spm-devices.c | 703 +++++++++++++++++++++ drivers/soc/qcom/spm.c | 482 ++++++++++++++ drivers/soc/qcom/spm_driver.h | 116 ++++ include/soc/qcom/spm.h | 70 ++ 6 files changed, 1435 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt create mode 100644 drivers/soc/qcom/spm-devices.c create mode 100644 drivers/soc/qcom/spm.c create mode 100644 drivers/soc/qcom/spm_driver.h create mode 100644 include/soc/qcom/spm.h diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt new file mode 100644 index 0000000..3130f4b --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt @@ -0,0 +1,62 @@ +* MSM Subsystem Power Manager (spm-v2) + +S4 generation of MSMs have SPM hardware blocks to control the Application +Processor Sub-System power. These SPM blocks run individual state machine +to determine what the core (L2 or Krait/Scorpion) would do when the WFI +instruction is executed by the core. + +The devicetree representation of the SPM block should be: + +Required properties + +- compatible: Could be one of - + "qcom,spm-v2.1" + "qcom,spm-v3.0" +- reg: The physical address and the size of the SPM's memory mapped registers +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets + that dont support CPU phandles the driver would support qcom,core-id. + This field is required on only for SPMs that control the CPU. +- qcom,saw2-cfg: SAW2 configuration register +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM + sequence +- qcom,saw2-spm-ctl: The SPM control register +- qcom,name: The name with which a SPM device is identified by the power + management code. + +Optional properties + +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS + (Fast Transient Switch) index to send the PMIC data to +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing + voltage +- qcom,phase-port: The PVC port used for changing the number of phases +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes +- qcom,saw2-spm-cmd-wfi: The WFI command sequence +- qcom,saw2-spm-cmd-ret: The Retention command sequence +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS + proc won't inform the RPM. +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may + turn off other SoC components. +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command + sequence. This sequence will retain the memory but turn off the logic. +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device + can control. +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to + change after sending the voltage command to the PMIC. +- +Example: + qcom,spm@f9089000 { + compatible = "qcom,spm-v2"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0xf9089000 0x1000>; + qcom,cpu = <&CPU0>; + qcom,saw2-cfg = <0x1>; + qcom,saw2-spm-dly= <0x20000400>; + qcom,saw2-spm-ctl = <0x1>; + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 + a0 b0 03 68 70 3b 92 a0 b0 + 82 2b 50 10 30 02 22 30 0f]; + }; diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 70d52ed..d7ae93b 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,3 +1,5 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o +obj-$(CONFIG_QCOM_PM) += spm-devices.o 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-devices.c b/drivers/soc/qcom/spm-devices.c new file mode 100644 index 0000000..567e9f9 --- /dev/null +++ b/drivers/soc/qcom/spm-devices.c @@ -0,0 +1,703 @@ +/* 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 +#include +#include +#include +#include + +#include + +#include "spm_driver.h" + +#define VDD_DEFAULT 0xDEADF00D + +struct msm_spm_power_modes { + uint32_t mode; + bool notify_rpm; + uint32_t start_addr; +}; + +struct msm_spm_device { + struct list_head list; + bool initialized; + const char *name; + struct msm_spm_driver_data reg_data; + struct msm_spm_power_modes *modes; + uint32_t num_modes; + uint32_t cpu_vdd; + struct cpumask mask; + void __iomem *q2s_reg; +}; + +struct msm_spm_vdd_info { + struct msm_spm_device *vctl_dev; + uint32_t vlevel; + int err; +}; + +static LIST_HEAD(spm_list); +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device); +static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device); + +static void msm_spm_smp_set_vdd(void *data) +{ + struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data; + struct msm_spm_device *dev = info->vctl_dev; + + dev->cpu_vdd = info->vlevel; + info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel); +} + +/** + * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2 + * probe. + * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER. + * if probe failed, then return the err number for that failure. + */ +int msm_spm_probe_done(void) +{ + struct msm_spm_device *dev; + int cpu; + int ret = 0; + + for_each_possible_cpu(cpu) { + dev = per_cpu(cpu_vctl_device, cpu); + if (!dev) + return -EPROBE_DEFER; + + ret = IS_ERR(dev); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(msm_spm_probe_done); + +void msm_spm_dump_regs(unsigned int cpu) +{ + dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu); +} + +/** + * msm_spm_set_vdd(): Set core voltage + * @cpu: core id + * @vlevel: Encoded PMIC data. + * + * Return: 0 on success or -(ERRNO) on failure. + */ +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) +{ + struct msm_spm_vdd_info info; + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + int ret; + + if (!dev) + return -EPROBE_DEFER; + + ret = IS_ERR(dev); + if (ret) + return ret; + + info.vctl_dev = dev; + info.vlevel = vlevel; + + ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info, + true); + if (ret) + return ret; + + return info.err; +} +EXPORT_SYMBOL(msm_spm_set_vdd); + +/** + * msm_spm_get_vdd(): Get core voltage + * @cpu: core id + * @return: Returns encoded PMIC data. + */ +unsigned int msm_spm_get_vdd(unsigned int cpu) +{ + int ret; + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -EPROBE_DEFER; + + ret = IS_ERR(dev); + if (ret) + return ret; + + return dev->cpu_vdd; +} +EXPORT_SYMBOL(msm_spm_get_vdd); + +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode) +{ + uint32_t spm_legacy_mode = 0; + uint32_t qchannel_ignore = 0; + uint32_t val = 0; + + if (!dev->q2s_reg) + return; + + switch (mode) { + case MSM_SPM_MODE_DISABLED: + case MSM_SPM_MODE_CLOCK_GATING: + qchannel_ignore = 1; + spm_legacy_mode = 0; + break; + case MSM_SPM_MODE_RETENTION: + qchannel_ignore = 0; + spm_legacy_mode = 0; + break; + case MSM_SPM_MODE_GDHS: + case MSM_SPM_MODE_POWER_COLLAPSE: + qchannel_ignore = 0; + spm_legacy_mode = 1; + break; + default: + break; + } + + val = spm_legacy_mode << 2 | qchannel_ignore << 1; + __raw_writel(val, dev->q2s_reg); + mb(); +} + +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm) +{ + uint32_t i; + uint32_t start_addr = 0; + int ret = -EINVAL; + bool pc_mode = false; + + if (!dev->initialized) + return -ENXIO; + + if ((mode == MSM_SPM_MODE_POWER_COLLAPSE) + || (mode == MSM_SPM_MODE_GDHS)) + pc_mode = true; + + if (mode == MSM_SPM_MODE_DISABLED) { + ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false); + } else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) { + for (i = 0; i < dev->num_modes; i++) { + if ((dev->modes[i].mode == mode) && + (dev->modes[i].notify_rpm == notify_rpm)) { + start_addr = dev->modes[i].start_addr; + break; + } + } + ret = msm_spm_drv_set_low_power_mode(&dev->reg_data, + start_addr, pc_mode); + } + + msm_spm_config_q2s(dev, mode); + + return ret; +} + +static int msm_spm_dev_init(struct msm_spm_device *dev, + struct msm_spm_platform_data *data) +{ + int i, ret = -ENOMEM; + uint32_t offset = 0; + + dev->cpu_vdd = VDD_DEFAULT; + dev->num_modes = data->num_modes; + dev->modes = kmalloc( + sizeof(struct msm_spm_power_modes) * dev->num_modes, + GFP_KERNEL); + + if (!dev->modes) + goto spm_failed_malloc; + + dev->reg_data.major = data->major; + dev->reg_data.minor = data->minor; + ret = msm_spm_drv_init(&dev->reg_data, data); + + if (ret) + goto spm_failed_init; + + for (i = 0; i < dev->num_modes; i++) { + + /* Default offset is 0 and gets updated as we write more + * sequences into SPM + */ + dev->modes[i].start_addr = offset; + ret = msm_spm_drv_write_seq_data(&dev->reg_data, + data->modes[i].cmd, &offset); + if (ret < 0) + goto spm_failed_init; + + dev->modes[i].mode = data->modes[i].mode; + dev->modes[i].notify_rpm = data->modes[i].notify_rpm; + } + msm_spm_drv_reinit(&dev->reg_data); + dev->initialized = true; + return 0; + +spm_failed_init: + kfree(dev->modes); +spm_failed_malloc: + return ret; +} + +/** + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core + * @base: The SAW VCTL register which would set the voltage up. + * @val: The value to be set on the rail + * @cpu: The cpu for this with rail is being powered on + */ +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu) +{ + uint32_t timeout = 2000; /* delay for voltage to settle on the core */ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + /* + * If clock drivers have already set up the voltage, + * do not overwrite that value. + */ + if (dev && (dev->cpu_vdd != VDD_DEFAULT)) + return 0; + + /* Set the CPU supply regulator voltage */ + val = (val & 0xFF); + writel_relaxed(val, base); + mb(); + udelay(timeout); + + /* Enable the CPU supply regulator*/ + val = 0x30080; + writel_relaxed(val, base); + mb(); + udelay(timeout); + + return 0; +} +EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail); + +void msm_spm_reinit(void) +{ + unsigned int cpu; + + for_each_possible_cpu(cpu) + msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu)); +} +EXPORT_SYMBOL(msm_spm_reinit); + +/* + * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu + * It should only be used to decide a mode before lpm driver is probed. + * @mode: SPM LPM mode to be selected + */ +bool msm_spm_is_mode_avail(unsigned int mode) +{ + struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); + int i; + + for (i = 0; i < dev->num_modes; i++) { + if (dev->modes[i].mode == mode) + return true; + } + + return false; +} + +/** + * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode + * @mode: SPM LPM mode to enter + * @notify_rpm: Notify RPM in this mode + */ +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) +{ + struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); + + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); +} +EXPORT_SYMBOL(msm_spm_set_low_power_mode); + +/** + * msm_spm_init(): Board initalization function + * @data: platform specific SPM register configuration data + * @nr_devs: Number of SPM devices being initialized + */ +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs) +{ + unsigned int cpu; + int ret = 0; + + BUG_ON((nr_devs < num_possible_cpus()) || !data); + + for_each_possible_cpu(cpu) { + struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu); + + ret = msm_spm_dev_init(dev, &data[cpu]); + if (ret < 0) { + pr_warn("%s():failed CPU:%u ret:%d\n", __func__, + cpu, ret); + break; + } + } + + return ret; +} + +struct msm_spm_device *msm_spm_get_device_by_name(const char *name) +{ + struct list_head *list; + + list_for_each(list, &spm_list) { + struct msm_spm_device *dev + = list_entry(list, typeof(*dev), list); + if (dev->name && !strcmp(dev->name, name)) + return dev; + } + return ERR_PTR(-ENODEV); +} + +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm) +{ + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); +} +#ifdef CONFIG_MSM_L2_SPM + +/** + * msm_spm_apcs_set_phase(): Set number of SMPS phases. + * @cpu: cpu which is requesting the change in number of phases. + * @phase_cnt: Number of phases to be set active + */ +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_pmic_data(&dev->reg_data, + MSM_SPM_PMIC_PHASE_PORT, phase_cnt); +} +EXPORT_SYMBOL(msm_spm_apcs_set_phase); + +/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power + * when the cores are in low power modes + * @cpu: cpu that is entering low power mode. + * @mode: The mode configuration for FTS + */ +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_pmic_data(&dev->reg_data, + MSM_SPM_PMIC_PFM_PORT, mode); +} +EXPORT_SYMBOL(msm_spm_enable_fts_lpm); + +#endif + +static int get_cpu_id(struct device_node *node) +{ + struct device_node *cpu_node; + u32 cpu; + int ret = -EINVAL; + char *key = "qcom,cpu"; + + cpu_node = of_parse_phandle(node, key, 0); + if (cpu_node) { + for_each_possible_cpu(cpu) { + if (of_get_cpu_node(cpu, NULL) == cpu_node) + return cpu; + } + } + return ret; +} + +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev) +{ + struct msm_spm_device *dev = NULL; + const char *val = NULL; + char *key = "qcom,name"; + int cpu = get_cpu_id(pdev->dev.of_node); + + if ((cpu >= 0) && cpu < num_possible_cpus()) + dev = &per_cpu(msm_cpu_spm_device, cpu); + else if ((cpu == 0xffff) || (cpu < 0)) + dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device), + GFP_KERNEL); + + if (!dev) + return NULL; + + if (of_property_read_string(pdev->dev.of_node, key, &val)) { + pr_err("%s(): Cannot find a required node key:%s\n", + __func__, key); + return NULL; + } + dev->name = val; + list_add(&dev->list, &spm_list); + + return dev; +} + +static void get_cpumask(struct device_node *node, struct cpumask *mask) +{ + unsigned long vctl_mask = 0; + unsigned c = 0; + int idx = 0; + struct device_node *cpu_node = NULL; + int ret = 0; + char *key = "qcom,cpu-vctl-list"; + bool found = false; + + cpu_node = of_parse_phandle(node, key, idx++); + while (cpu_node) { + found = true; + for_each_possible_cpu(c) { + if (of_get_cpu_node(c, NULL) == cpu_node) + cpumask_set_cpu(c, mask); + } + cpu_node = of_parse_phandle(node, key, idx++); + }; + + if (found) + return; + + key = "qcom,cpu-vctl-mask"; + ret = of_property_read_u32(node, key, (u32 *) &vctl_mask); + if (!ret) { + for_each_set_bit(c, &vctl_mask, num_possible_cpus()) { + cpumask_set_cpu(c, mask); + } + } +} + +static int msm_spm_dev_probe(struct platform_device *pdev) +{ + int ret = 0; + int cpu = 0; + int i = 0; + struct device_node *node = pdev->dev.of_node; + struct msm_spm_platform_data spm_data; + char *key = NULL; + uint32_t val = 0; + struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR]; + int len = 0; + struct msm_spm_device *dev = NULL; + struct resource *res = NULL; + uint32_t mode_count = 0; + + struct spm_of { + char *key; + uint32_t id; + }; + + struct spm_of spm_of_data[] = { + {"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG}, + {"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY}, + {"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL}, + {"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0}, + {"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1}, + {"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2}, + {"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3}, + {"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4}, + {"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5}, + {"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6}, + {"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7}, + }; + + struct mode_of { + char *key; + uint32_t id; + uint32_t notify_rpm; + }; + + struct mode_of mode_of_data[] = { + {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0}, + {"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0}, + {"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1}, + {"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0}, + {"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1}, + }; + + dev = msm_spm_get_device(pdev); + if (!dev) { + ret = -ENOMEM; + goto fail; + } + get_cpumask(node, &dev->mask); + + memset(&spm_data, 0, sizeof(struct msm_spm_platform_data)); + memset(&modes, 0, + (MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry)); + + if (of_device_is_compatible(node, "qcom,spm-v2.1")) { + spm_data.major = 2; + spm_data.minor = 1; + } else if (of_device_is_compatible(node, "qcom,spm-v3.0")) { + spm_data.major = 3; + spm_data.minor = 0; + } + + key = "qcom,vctl-timeout-us"; + ret = of_property_read_u32(node, key, &val); + if (!ret) + spm_data.vctl_timeout_us = val; + + /* SAW start address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -EFAULT; + goto fail; + } + + spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!spm_data.reg_base_addr) { + ret = -ENOMEM; + goto fail; + } + + spm_data.vctl_port = -1; + spm_data.phase_port = -1; + spm_data.pfm_port = -1; + + key = "qcom,vctl-port"; + of_property_read_u32(node, key, &spm_data.vctl_port); + + key = "qcom,phase-port"; + of_property_read_u32(node, key, &spm_data.phase_port); + + key = "qcom,pfm-port"; + of_property_read_u32(node, key, &spm_data.pfm_port); + + /* Q2S (QChannel-2-SPM) register */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + dev->q2s_reg = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!dev->q2s_reg) { + pr_err("%s(): Unable to iomap Q2S register\n", + __func__); + ret = -EADDRNOTAVAIL; + goto fail; + } + } + /* + * At system boot, cpus and or clusters can remain in reset. CCI SPM + * will not be triggered unless SPM_LEGACY_MODE bit is set for the + * cluster in reset. Initialize q2s registers and set the + * SPM_LEGACY_MODE bit. + */ + msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE); + + for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) { + ret = of_property_read_u32(node, spm_of_data[i].key, &val); + if (ret) + continue; + spm_data.reg_init_values[spm_of_data[i].id] = val; + } + + for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) { + key = mode_of_data[i].key; + modes[mode_count].cmd = + (uint8_t *)of_get_property(node, key, &len); + if (!modes[mode_count].cmd) + continue; + modes[mode_count].mode = mode_of_data[i].id; + modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm; + pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__, + dev->name, key, modes[mode_count].mode, + modes[mode_count].notify_rpm); + mode_count++; + } + + spm_data.modes = modes; + spm_data.num_modes = mode_count; + + ret = msm_spm_dev_init(dev, &spm_data); + if (ret) + goto fail; + + platform_set_drvdata(pdev, dev); + + for_each_cpu(cpu, &dev->mask) + per_cpu(cpu_vctl_device, cpu) = dev; + + return ret; + +fail: + cpu = get_cpu_id(pdev->dev.of_node); + if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) { + for_each_cpu(cpu, &dev->mask) + per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret); + } + + pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret); + + return ret; +} + +static int msm_spm_dev_remove(struct platform_device *pdev) +{ + struct msm_spm_device *dev = platform_get_drvdata(pdev); + + list_del(&dev->list); + + return 0; +} + +static struct of_device_id msm_spm_match_table[] = { + {.compatible = "qcom,spm-v2.1"}, + {.compatible = "qcom,spm-v3.0"}, + {}, +}; + +static struct platform_driver msm_spm_device_driver = { + .probe = msm_spm_dev_probe, + .remove = msm_spm_dev_remove, + .driver = { + .name = "spm-v2", + .owner = THIS_MODULE, + .of_match_table = msm_spm_match_table, + }, +}; + +/** + * msm_spm_device_init(): Device tree initialization function + */ +int __init msm_spm_device_init(void) +{ + static bool registered; + + if (registered) + return 0; + + registered = true; + + return platform_driver_register(&msm_spm_device_driver); +} +device_initcall(msm_spm_device_init); diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c new file mode 100644 index 0000000..7dbdb64 --- /dev/null +++ b/drivers/soc/qcom/spm.c @@ -0,0 +1,482 @@ +/* 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_driver.h" + +#define MSM_SPM_PMIC_STATE_IDLE 0 + +enum { + MSM_SPM_DEBUG_SHADOW = 1U << 0, + MSM_SPM_DEBUG_VCTL = 1U << 1, +}; + +static int msm_spm_debug_mask; +module_param_named( + debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + +struct saw2_data { + const char *ver_name; + uint32_t major; + uint32_t minor; + uint32_t *spm_reg_offset_ptr; +}; + +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 uint32_t msm_spm_reg_offsets_saw2_v3_0[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_STS2] = 0x38, + [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] = 0x400, + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, +}; + +static struct saw2_data saw2_info[] = { + [0] = { + "SAW2_v2.1", + 2, + 1, + msm_spm_reg_offsets_saw2_v2_1, + }, + [1] = { + "SAW2_v3.0", + 3, + 0, + msm_spm_reg_offsets_saw2_v3_0, + }, +}; + +static uint32_t num_pmic_data; + +static inline uint32_t msm_spm_drv_get_num_spm_entry( + struct msm_spm_driver_data *dev) +{ + return 32; +} + +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, + unsigned int reg_index) +{ + __raw_writel(dev->reg_shadow[reg_index], + dev->reg_base_addr + dev->reg_offsets[reg_index]); +} + +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, + unsigned int reg_index) +{ + dev->reg_shadow[reg_index] = + __raw_readl(dev->reg_base_addr + + dev->reg_offsets[reg_index]); +} + +static inline void msm_spm_drv_set_start_addr( + struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode) +{ + addr &= 0x7F; + addr <<= 4; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; + + if (dev->major != 0x3) + return; + + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF; + if (pc_mode) + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000; +} + +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1; +} + +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, + uint32_t vlevel) +{ + unsigned int pmic_data = 0; + + /** + * VCTL_PORT has to be 0, for PMIC_STS register to be updated. + * Ensure that vctl_port is always set to 0. + */ + WARN_ON(dev->vctl_port); + + pmic_data |= vlevel; + pmic_data |= (dev->vctl_port & 0x7) << 16; + + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; + + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3); +} + +static inline uint32_t msm_spm_drv_get_num_pmic_data( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); + mb(); + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7; +} + +static inline uint32_t msm_spm_drv_get_sts_pmic_state( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); + return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) & + 0x03; +} + +uint32_t msm_spm_drv_get_sts_curr_pmic_data( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); + return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF; +} + +inline int msm_spm_drv_set_spm_enable( + struct msm_spm_driver_data *dev, bool enable) +{ + uint32_t value = enable ? 0x01 : 0x00; + + if (!dev) + return -EINVAL; + + if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { + + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); + wmb(); + } + return 0; +} +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev) +{ + int i; + int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); + + if (!dev) { + __WARN(); + return; + } + + for (i = 0; i < num_spm_entry; i++) { + __raw_writel(dev->reg_seq_entry_shadow[i], + dev->reg_base_addr + + dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] + + 4 * i); + } + mb(); +} + +void dump_regs(struct msm_spm_driver_data *dev, int cpu) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); + mb(); + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu, + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]); + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); + mb(); + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu, + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]); +} + +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, + uint8_t *cmd, uint32_t *offset) +{ + uint32_t cmd_w; + uint32_t offset_w = *offset / 4; + uint8_t last_cmd; + + if (!cmd) + return -EINVAL; + + while (1) { + int i; + + cmd_w = 0; + last_cmd = 0; + cmd_w = dev->reg_seq_entry_shadow[offset_w]; + + for (i = (*offset % 4); i < 4; i++) { + last_cmd = *(cmd++); + cmd_w |= last_cmd << (i * 8); + (*offset)++; + if (last_cmd == 0x0f) + break; + } + + dev->reg_seq_entry_shadow[offset_w++] = cmd_w; + if (last_cmd == 0x0f) + break; + } + + return 0; +} + +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, + uint32_t addr, bool pc_mode) +{ + + if (!dev) + return -EINVAL; + + msm_spm_drv_set_start_addr(dev, addr, pc_mode); + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); + wmb(); + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) { + int i; + + for (i = 0; i < MSM_SPM_REG_NR; i++) + pr_info("%s: reg %02x = 0x%08x\n", __func__, + dev->reg_offsets[i], dev->reg_shadow[i]); + } + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); + + return 0; +} + +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel) +{ + uint32_t timeout_us, new_level; + + if (!dev) + return -EINVAL; + + if (!msm_spm_pmic_arb_present(dev)) + return -ENOSYS; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: requesting vlevel %#x\n", __func__, vlevel); + + /* Kick the state machine back to idle */ + dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST); + + msm_spm_drv_set_vctl2(dev, vlevel); + + timeout_us = dev->vctl_timeout_us; + /* Confirm the voltage we set was what hardware sent */ + do { + new_level = msm_spm_drv_get_sts_curr_pmic_data(dev); + if (new_level == vlevel) + break; + udelay(1); + } while (--timeout_us); + if (!timeout_us) { + pr_info("Wrong level %#x\n", new_level); + goto set_vdd_bail; + } + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: done, remaining timeout %u us\n", + __func__, timeout_us); + + return 0; + +set_vdd_bail: + pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n", + __func__, vlevel, timeout_us, new_level); + return -EIO; +} + +static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev, + enum msm_spm_pmic_port port) +{ + int index = -1; + + switch (port) { + case MSM_SPM_PMIC_VCTL_PORT: + index = dev->vctl_port; + break; + case MSM_SPM_PMIC_PHASE_PORT: + index = dev->phase_port; + break; + case MSM_SPM_PMIC_PFM_PORT: + index = dev->pfm_port; + break; + default: + break; + } + + return index; +} + +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, + enum msm_spm_pmic_port port, unsigned int data) +{ + unsigned int pmic_data = 0; + unsigned int timeout_us = 0; + int index = 0; + + if (!msm_spm_pmic_arb_present(dev)) + return -ENOSYS; + + index = msm_spm_drv_get_pmic_port(dev, port); + if (index < 0) + return -ENODEV; + + pmic_data |= data & 0xFF; + pmic_data |= (index & 0x7) << 16; + + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); + mb(); + + timeout_us = dev->vctl_timeout_us; + /** + * Confirm the pmic data set was what hardware sent by + * checking the PMIC FSM state. + * We cannot use the sts_pmic_data and check it against + * the value like we do fot set_vdd, since the PMIC_STS + * is only updated for SAW_VCTL sent with port index 0. + */ + do { + if (msm_spm_drv_get_sts_pmic_state(dev) == + MSM_SPM_PMIC_STATE_IDLE) + break; + udelay(1); + } while (--timeout_us); + + if (!timeout_us) { + pr_err("%s: failed, remaining timeout %u us, data %d\n", + __func__, timeout_us, data); + return -EIO; + } + + return 0; +} + +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev) +{ + int i; + + msm_spm_drv_flush_seq_entry(dev); + for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++) + msm_spm_drv_flush_shadow(dev, i); + + mb(); + + for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++) + msm_spm_drv_load_shadow(dev, i); +} + +int msm_spm_drv_init(struct msm_spm_driver_data *dev, + struct msm_spm_platform_data *data) +{ + int i; + int num_spm_entry; + bool found = false; + + BUG_ON(!dev || !data); + + dev->vctl_port = data->vctl_port; + dev->phase_port = data->phase_port; + dev->pfm_port = data->pfm_port; + dev->reg_base_addr = data->reg_base_addr; + memcpy(dev->reg_shadow, data->reg_init_values, + sizeof(data->reg_init_values)); + + dev->vctl_timeout_us = data->vctl_timeout_us; + + for (i = 0; i < ARRAY_SIZE(saw2_info); i++) + if (dev->major == saw2_info[i].major && + dev->minor == saw2_info[i].minor) { + pr_debug("%s: Version found\n", + saw2_info[i].ver_name); + dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr; + found = true; + break; + } + + if (!found) { + pr_err("%s: No SAW2 version found\n", __func__); + BUG_ON(!found); + } + + if (!num_pmic_data) + num_pmic_data = msm_spm_drv_get_num_pmic_data(dev); + + num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); + + dev->reg_seq_entry_shadow = + kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry, + GFP_KERNEL); + + if (!dev->reg_seq_entry_shadow) + return -ENOMEM; + + return 0; +} diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h new file mode 100644 index 0000000..b306520 --- /dev/null +++ b/drivers/soc/qcom/spm_driver.h @@ -0,0 +1,116 @@ +/* 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 + +#include + +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_seq_entry { + uint32_t mode; + uint8_t *cmd; + bool notify_rpm; +}; + +struct msm_spm_platform_data { + void __iomem *reg_base_addr; + uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE]; + + uint32_t major; + uint32_t minor; + uint32_t vctl_port; + uint32_t phase_port; + uint32_t pfm_port; + + uint8_t awake_vlevel; + uint32_t vctl_timeout_us; + + uint32_t num_modes; + struct msm_spm_seq_entry *modes; +}; + +enum msm_spm_pmic_port { + MSM_SPM_PMIC_VCTL_PORT, + MSM_SPM_PMIC_PHASE_PORT, + MSM_SPM_PMIC_PFM_PORT, +}; + +struct msm_spm_driver_data { + uint32_t major; + uint32_t minor; + uint32_t vctl_port; + uint32_t phase_port; + uint32_t pfm_port; + void __iomem *reg_base_addr; + uint32_t vctl_timeout_us; + uint32_t reg_shadow[MSM_SPM_REG_NR]; + uint32_t *reg_seq_entry_shadow; + uint32_t *reg_offsets; +}; + +int msm_spm_drv_init(struct msm_spm_driver_data *dev, + struct msm_spm_platform_data *data); +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev); +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, + uint32_t addr, bool pc_mode); +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, + unsigned int vlevel); +void dump_regs(struct msm_spm_driver_data *dev, int cpu); +uint32_t msm_spm_drv_get_sts_curr_pmic_data( + struct msm_spm_driver_data *dev); +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, + uint8_t *cmd, uint32_t *offset); +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev); +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev, + bool enable); +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, + enum msm_spm_pmic_port port, unsigned int data); + +void msm_spm_reinit(void); +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs); + +#endif /* __QCOM_SPM_DRIVER_H */ diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h new file mode 100644 index 0000000..f39e0c4 --- /dev/null +++ b/include/soc/qcom/spm.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2010-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_H +#define __QCOM_SPM_H + +enum { + MSM_SPM_MODE_DISABLED, + MSM_SPM_MODE_CLOCK_GATING, + MSM_SPM_MODE_RETENTION, + MSM_SPM_MODE_GDHS, + MSM_SPM_MODE_POWER_COLLAPSE, + MSM_SPM_MODE_NR +}; + +struct msm_spm_device; + +#if defined(CONFIG_QCOM_PM) +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm); +int msm_spm_probe_done(void); +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel); +unsigned int msm_spm_get_vdd(unsigned int cpu); +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu); +struct msm_spm_device *msm_spm_get_device_by_name(const char *name); +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm); +int msm_spm_device_init(void); +bool msm_spm_is_mode_avail(unsigned int mode); +void msm_spm_dump_regs(unsigned int cpu); +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt); +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode); +#else /* defined(CONFIG_QCOM_PM) */ +static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) +{ return -ENOSYS; } +static inline int msm_spm_probe_done(void) +{ return -ENOSYS; } +static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) +{ return -ENOSYS; } +static inline unsigned int msm_spm_get_vdd(unsigned int cpu) +{ return 0; } +static inline int msm_spm_turn_on_cpu_rail(void __iomem *base, + unsigned int val, int cpu) +{ return -ENOSYS; } +static inline int msm_spm_device_init(void) +{ return -ENOSYS; } +static void msm_spm_dump_regs(unsigned int cpu) {} +static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm) +{ return -ENODEV; } +static inline struct msm_spm_device *msm_spm_get_device_by_name( + const char *name) +{ return NULL; } +static inline bool msm_spm_is_mode_avail(unsigned int mode) +{ return false; } +static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) +{ return -ENOSYS; } +static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) +{ return -ENOSYS; } +#endif /* defined (CONFIG_QCOM_PM) */ + +#endif /* __QCOM_SPM_H */