From patchwork Sun Jul 2 17:42:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Baryshkov X-Patchwork-Id: 13299303 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 74F3AC001E0 for ; Sun, 2 Jul 2023 17:43:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229990AbjGBRnX (ORCPT ); Sun, 2 Jul 2023 13:43:23 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:40894 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229989AbjGBRm7 (ORCPT ); Sun, 2 Jul 2023 13:42:59 -0400 Received: from mail-lj1-x22a.google.com (mail-lj1-x22a.google.com [IPv6:2a00:1450:4864:20::22a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 21C5CE73 for ; Sun, 2 Jul 2023 10:42:57 -0700 (PDT) Received: by mail-lj1-x22a.google.com with SMTP id 38308e7fff4ca-2b6adef5c22so55947091fa.3 for ; Sun, 02 Jul 2023 10:42:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1688319775; x=1690911775; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=bK8WPsZQis5KBu/8sVzWIZ3oMBJ8zoaGC76tJyGSV0I=; b=FXHb7zM8rJO/E+7tVzZGPsJMPoCnqfzWIaIl/7yWAOoC3mW/5SLys0l978waAqzF9+ 5xCdRg4RIcc5P6gQMzqc94cGv3je/ZDZuVlcYITCt8UMidS3p/ENarNjqUe4xuMQXiHh /8MUxUMsBtp6i1+M42rlBV4QEdOlNmenk3dQJ7Zv1fpkExzI0GGRonJRdTFqCtSoMX8X IFrYYftXKOQOCdldNvNTzEO8tDKQFqdCy5bZydS1IXKovR4b0Gim3eyR9fbfJjXi/AMG eUj0ACVEJ+B/aLo2/1E4gDWZIqgeSm2U+kjVg4E8PJVDIPStNOKUvIq7HqkkH9qOeEET NohQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1688319775; x=1690911775; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=bK8WPsZQis5KBu/8sVzWIZ3oMBJ8zoaGC76tJyGSV0I=; b=i27W7R3V5ngQUeWJ+aXEB7YOcSgJPM0x8SHTSeWX4HIzn16JoxHm2ydNxrCqVJdgwh XTIY5s/Z37P73vnlQSZABCU4LJX5QFcJx8RZWYZKMdfRWVy+6hv6kJ8nEOkgM/n2Bxbi uwn4OjAG/lQ/LW5rrpnc68X5HOHq0q66GSjWs022KWUA4tr/hPEmpmFpYA//frqppnJF /gkJcJ0ytnB2uaXZGzKDE2cTSqwRFTNqbon1SVh22JjfdAWh+UGKXYhu8j7ONCi8D9dI 0l7Jq8RK05Cskovp2YYnz2woaTlxCDnOikafiotlNdZKgXwa+fIl8w/VnSz2TjUXzWj6 9VLA== X-Gm-Message-State: ABy/qLbHhAtfpT01Xn9Pss7FJcR6a8EjVJwI1+VIhc863b5i54gIrhy0 0ZCk/kMCs0cx8F+MNxcihxZK2Q== X-Google-Smtp-Source: APBJJlGF/BMevvwYJuT2W41j7gotm6RZd7WYOC74DkFnCURRwnfcN6FK/LjQMwtye33AX64BGuWzwQ== X-Received: by 2002:a2e:9dc8:0:b0:2b6:d603:7667 with SMTP id x8-20020a2e9dc8000000b002b6d6037667mr4662751ljj.8.1688319775317; Sun, 02 Jul 2023 10:42:55 -0700 (PDT) Received: from umbar.unikie.fi ([192.130.178.91]) by smtp.gmail.com with ESMTPSA id v23-20020a2e9917000000b002b6daa3fa2csm1372550lji.69.2023.07.02.10.42.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 02 Jul 2023 10:42:54 -0700 (PDT) From: Dmitry Baryshkov To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andy Gross , Bjorn Andersson , Konrad Dybcio , Ilia Lin , Viresh Kumar , Nishanth Menon , Stephen Boyd , Michael Turquette , "Rafael J. Wysocki" , Georgi Djakov Cc: linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org, linux-pm@vger.kernel.org, linux-clk@vger.kernel.org, Christian Marangi , Stephan Gerhold Subject: [PATCH v3 08/28] soc: qcom: spm: add support for voltage regulator Date: Sun, 2 Jul 2023 20:42:26 +0300 Message-Id: <20230702174246.121656-9-dmitry.baryshkov@linaro.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230702174246.121656-1-dmitry.baryshkov@linaro.org> References: <20230702174246.121656-1-dmitry.baryshkov@linaro.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The SPM / SAW2 device also provides a voltage regulator functionality with optional AVS (Adaptive Voltage Scaling) support. The exact register sequence and voltage ranges differs from device to device. Signed-off-by: Dmitry Baryshkov --- drivers/soc/qcom/spm.c | 205 ++++++++++++++++++++++++++++++++++++++++- include/soc/qcom/spm.h | 9 ++ 2 files changed, 212 insertions(+), 2 deletions(-) diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c index a6cbeb40831b..3c16a7e1710c 100644 --- a/drivers/soc/qcom/spm.c +++ b/drivers/soc/qcom/spm.c @@ -9,19 +9,31 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include +#include +#include #include +#define FIELD_SET(current, mask, val) \ + (((current) & ~(mask)) | FIELD_PREP((mask), (val))) + #define SPM_CTL_INDEX 0x7f #define SPM_CTL_INDEX_SHIFT 4 #define SPM_CTL_EN BIT(0) +#define SPM_1_1_AVS_CTL_AVS_ENABLED BIT(27) +#define SPM_AVS_CTL_MIN_VLVL (0x3f << 10) +#define SPM_AVS_CTL_MAX_VLVL (0x3f << 17) + enum spm_reg { SPM_REG_CFG, SPM_REG_SPM_CTL, @@ -31,10 +43,12 @@ enum spm_reg { SPM_REG_PMIC_DATA_1, SPM_REG_VCTL, SPM_REG_SEQ_ENTRY, - SPM_REG_SPM_STS, + SPM_REG_STS0, + SPM_REG_STS1, SPM_REG_PMIC_STS, SPM_REG_AVS_CTL, SPM_REG_AVS_LIMIT, + SPM_REG_RST, SPM_REG_NR, }; @@ -171,6 +185,10 @@ static const struct spm_reg_data spm_reg_8226_cpu = { static const u16 spm_reg_offset_v1_1[SPM_REG_NR] = { [SPM_REG_CFG] = 0x08, + [SPM_REG_STS0] = 0x0c, + [SPM_REG_STS1] = 0x10, + [SPM_REG_VCTL] = 0x14, + [SPM_REG_AVS_CTL] = 0x18, [SPM_REG_SPM_CTL] = 0x20, [SPM_REG_PMIC_DLY] = 0x24, [SPM_REG_PMIC_DATA_0] = 0x28, @@ -178,7 +196,12 @@ static const u16 spm_reg_offset_v1_1[SPM_REG_NR] = { [SPM_REG_SEQ_ENTRY] = 0x80, }; +static void smp_set_vdd_v1_1(void *data); + /* SPM register data for 8064 */ +static struct linear_range spm_v1_1_regulator_range = + REGULATOR_LINEAR_RANGE(700000, 0, 56, 12500); + static const struct spm_reg_data spm_reg_8064_cpu = { .reg_offset = spm_reg_offset_v1_1, .spm_cfg = 0x1F, @@ -189,6 +212,10 @@ static const struct spm_reg_data spm_reg_8064_cpu = { 0x10, 0x54, 0x30, 0x0C, 0x24, 0x30, 0x0F }, .start_index[PM_SLEEP_MODE_STBY] = 0, .start_index[PM_SLEEP_MODE_SPC] = 2, + .set_vdd = smp_set_vdd_v1_1, + .range = &spm_v1_1_regulator_range, + .init_uV = 1300000, + .ramp_delay = 1250, }; static inline void spm_register_write(struct spm_driver_data *drv, @@ -240,6 +267,179 @@ void spm_set_low_power_mode(struct spm_driver_data *drv, spm_register_write_sync(drv, SPM_REG_SPM_CTL, ctl_val); } +static int spm_set_voltage_sel(struct regulator_dev *rdev, unsigned int selector) +{ + struct spm_driver_data *drv = rdev_get_drvdata(rdev); + + drv->volt_sel = selector; + + /* Always do the SAW register writes on the corresponding CPU */ + return smp_call_function_single(drv->reg_cpu, drv->reg_data->set_vdd, drv, true); +} + +static int spm_get_voltage_sel(struct regulator_dev *rdev) +{ + struct spm_driver_data *drv = rdev_get_drvdata(rdev); + + return drv->volt_sel; +} + +static const struct regulator_ops spm_reg_ops = { + .set_voltage_sel = spm_set_voltage_sel, + .get_voltage_sel = spm_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +static void smp_set_vdd_v1_1(void *data) +{ + struct spm_driver_data *drv = data; + unsigned int vlevel = drv->volt_sel; + unsigned int vctl, data0, data1, avs_ctl, sts; + bool avs_enabled; + + vlevel |= 0x80; /* band */ + + avs_ctl = spm_register_read(drv, SPM_REG_AVS_CTL); + vctl = spm_register_read(drv, SPM_REG_VCTL); + data0 = spm_register_read(drv, SPM_REG_PMIC_DATA_0); + data1 = spm_register_read(drv, SPM_REG_PMIC_DATA_1); + + avs_enabled = avs_ctl & SPM_1_1_AVS_CTL_AVS_ENABLED; + + /* If AVS is enabled, switch it off during the voltage change */ + if (avs_enabled) { + avs_ctl &= ~SPM_1_1_AVS_CTL_AVS_ENABLED; + spm_register_write(drv, SPM_REG_AVS_CTL, avs_ctl); + } + + /* Kick the state machine back to idle */ + spm_register_write(drv, SPM_REG_RST, 1); + + vctl = FIELD_SET(vctl, 0xff, vlevel); + data0 = FIELD_SET(data0, 0xff, vlevel); + data1 = FIELD_SET(data1, 0x3f, vlevel); + data1 = FIELD_SET(data1, 0x3f << 16, vlevel); + + spm_register_write(drv, SPM_REG_VCTL, vctl); + spm_register_write(drv, SPM_REG_PMIC_DATA_0, data0); + spm_register_write(drv, SPM_REG_PMIC_DATA_1, data1); + + if (read_poll_timeout_atomic(spm_register_read, + sts, sts == vlevel, + 1, 200, false, + drv, SPM_REG_STS1)) { + dev_err_ratelimited(drv->dev, "timeout setting the voltage (%x %x)!\n", sts, vlevel); + goto enable_avs; + } + + if (avs_enabled) { + unsigned int max_avs = vlevel & 0x3f; + unsigned int min_avs = max(max_avs, 4U) - 4; + avs_ctl = FIELD_SET(avs_ctl, SPM_AVS_CTL_MIN_VLVL, min_avs); + avs_ctl = FIELD_SET(avs_ctl, SPM_AVS_CTL_MAX_VLVL, max_avs); + spm_register_write(drv, SPM_REG_AVS_CTL, avs_ctl); + } + +enable_avs: + if (avs_enabled) { + avs_ctl |= SPM_1_1_AVS_CTL_AVS_ENABLED; + spm_register_write(drv, SPM_REG_AVS_CTL, avs_ctl); + } +} + +static int spm_get_cpu(struct device *dev) +{ + int cpu; + bool found; + + for_each_possible_cpu(cpu) { + struct device_node *cpu_node, *saw_node; + + cpu_node = of_cpu_device_node_get(cpu); + if (!cpu_node) + continue; + + saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0); + found = (saw_node == dev->of_node); + of_node_put(saw_node); + of_node_put(cpu_node); + + if (found) + return cpu; + } + + /* L2 SPM is not bound to any CPU, tie it to CPU0 */ + + return 0; +} + +#ifdef CONFIG_REGULATOR +static int spm_register_regulator(struct device *dev, struct spm_driver_data *drv) +{ + struct regulator_config config = { + .dev = dev, + .driver_data = drv, + }; + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + int ret; + bool found; + + if (!drv->reg_data->set_vdd) + return 0; + + rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL); + if (!rdesc) + return -ENOMEM; + + rdesc->name = "spm"; + rdesc->of_match = of_match_ptr("regulator"); + rdesc->type = REGULATOR_VOLTAGE; + rdesc->owner = THIS_MODULE; + rdesc->ops = &spm_reg_ops; + + rdesc->linear_ranges = drv->reg_data->range; + rdesc->n_linear_ranges = 1; + rdesc->n_voltages = rdesc->linear_ranges[rdesc->n_linear_ranges - 1].max_sel + 1; + rdesc->ramp_delay = drv->reg_data->ramp_delay; + + drv->reg_cpu = spm_get_cpu(dev); + dev_dbg(dev, "SAW2 bound to CPU %d\n", drv->reg_cpu); + + /* + * Program initial voltage, otherwise registration will also try + * setting the voltage, which might result in undervolting the CPU. + */ + drv->volt_sel = DIV_ROUND_UP(drv->reg_data->init_uV - rdesc->min_uV, + rdesc->uV_step); + ret = linear_range_get_selector_high(drv->reg_data->range, + drv->reg_data->init_uV, + &drv->volt_sel, + &found); + if (ret) { + dev_err(dev, "Initial uV value out of bounds\n"); + return ret; + } + + /* Always do the SAW register writes on the corresponding CPU */ + smp_call_function_single(drv->reg_cpu, drv->reg_data->set_vdd, drv, true); + + rdev = devm_regulator_register(dev, rdesc, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register regulator\n"); + return PTR_ERR(rdev); + } + + return 0; +} +#else +static int spm_register_regulator(struct device *dev, struct spm_driver_data *drv) +{ + return 0; +} +#endif + static const struct of_device_id spm_match_table[] = { { .compatible = "qcom,sdm660-gold-saw2-v4.1-l2", .data = &spm_reg_660_gold_l2 }, @@ -292,6 +492,7 @@ static int spm_dev_probe(struct platform_device *pdev) return -ENODEV; drv->reg_data = match_id->data; + drv->dev = &pdev->dev; platform_set_drvdata(pdev, drv); /* Write the SPM sequences first.. */ @@ -319,7 +520,7 @@ static int spm_dev_probe(struct platform_device *pdev) if (drv->reg_data->reg_offset[SPM_REG_SPM_CTL]) spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY); - return 0; + return spm_register_regulator(&pdev->dev, drv); } static struct platform_driver spm_driver = { diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h index 4951f9d8b0bd..9859ebe42003 100644 --- a/include/soc/qcom/spm.h +++ b/include/soc/qcom/spm.h @@ -30,11 +30,20 @@ struct spm_reg_data { u32 avs_limit; u8 seq[MAX_SEQ_DATA]; u8 start_index[PM_SLEEP_MODE_NR]; + + smp_call_func_t set_vdd; + /* for now we support only a single range */ + struct linear_range *range; + unsigned int ramp_delay; + unsigned int init_uV; }; struct spm_driver_data { void __iomem *reg_base; const struct spm_reg_data *reg_data; + struct device *dev; + unsigned int volt_sel; + int reg_cpu; }; void spm_set_low_power_mode(struct spm_driver_data *drv,