From ee98a7dd2dce4485e9faedcb7649f0554750eb80 Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Wed, 16 Nov 2016 17:20:24 +0530 Subject: [PATCH 01/35] HACK: don't break GIC --- drivers/irqchip/irq-gic-v3.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 5a67ec08458871..8f2b78e423e434 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -139,6 +139,8 @@ static void gic_enable_redist(bool enable) u32 count = 1000000; /* 1s! */ u32 val; + return; + rbase = gic_data_rdist_rd_base(); val = readl_relaxed(rbase + GICR_WAKER); @@ -674,8 +676,8 @@ static void gic_cpu_init(void) gic_cpu_config(rbase, gic_redist_wait_for_rwp); /* Give LPIs a spin */ - if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) - its_cpu_init(); + //if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) + // its_cpu_init(); /* initialise system registers */ gic_cpu_sys_reg_init(); @@ -1123,8 +1125,8 @@ static int __init gic_init_bases(void __iomem *dist_base, gic_update_vlpi_properties(); - if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) - its_init(handle, &gic_data.rdists, gic_data.domain); + //if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) + // its_init(handle, &gic_data.rdists, gic_data.domain); gic_smp_init(); gic_dist_init(); From 76951f5182de201c9f68589f759d52ef4a841619 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 9 Dec 2016 10:55:33 +0000 Subject: [PATCH 02/35] hack disable uart0 Signed-off-by: Srinivas Kandagatla --- arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi index 0f829db33efe2d..8f88315e39535a 100644 --- a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi +++ b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi @@ -73,13 +73,13 @@ pinctrl-1 = <&blsp2_uart1_2pins_sleep>; }; - serial@75b1000 { - label = "LS-UART0"; - status = "okay"; - pinctrl-names = "default", "sleep"; - pinctrl-0 = <&blsp2_uart2_4pins_default>; - pinctrl-1 = <&blsp2_uart2_4pins_sleep>; - }; +// serial@75b1000 { +// label = "LS-UART0"; +// status = "okay"; +// pinctrl-names = "default", "sleep"; +// pinctrl-0 = <&blsp2_uart2_4pins_default>; +// pinctrl-1 = <&blsp2_uart2_4pins_sleep>; +// }; i2c@7577000 { /* On Low speed expansion */ From dea3fe3769ec85f3e98d1abab946f1a8cc489183 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Wed, 2 May 2018 13:45:06 -0700 Subject: [PATCH 03/35] defconfig --- arch/arm64/configs/defconfig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 3cfa8ca2673846..6cb98ded4410b0 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -392,6 +392,21 @@ CONFIG_MFD_EXYNOS_LPASS=m CONFIG_MFD_HI6421_PMIC=y CONFIG_MFD_HI655X_PMIC=y CONFIG_MFD_MAX77620=y +# CONFIG_MFD_MAX77686 is not set +# CONFIG_MFD_MAX77693 is not set +# CONFIG_MFD_MAX77843 is not set +# CONFIG_MFD_MAX8907 is not set +# CONFIG_MFD_MAX8925 is not set +# CONFIG_MFD_MAX8997 is not set +# CONFIG_MFD_MAX8998 is not set +# CONFIG_MFD_MT6397 is not set +# CONFIG_MFD_MENF21BMC is not set +# CONFIG_EZX_PCAP is not set +# CONFIG_MFD_CPCAP is not set +# CONFIG_MFD_VIPERBOARD is not set +# CONFIG_MFD_RETU is not set +# CONFIG_MFD_PCF50633 is not set +CONFIG_MFD_QCOM_RPM=y CONFIG_MFD_SPMI_PMIC=y CONFIG_MFD_RK808=y CONFIG_MFD_SEC_CORE=y @@ -708,3 +723,9 @@ CONFIG_CRYPTO_AES_ARM64_BS=m CONFIG_CRYPTO_SHA512_ARM64_CE=m CONFIG_CRYPTO_SHA3_ARM64=m CONFIG_CRYPTO_SM3_ARM64_CE=m +CONFIG_RPMSG=y +CONFIG_RPMSG_QCOM_GLINK_NATIVE=y +CONFIG_RPMSG_QCOM_GLINK_RPM=y +CONFIG_RPMSG_QCOM_GLINK_SMEM=y +CONFIG_QCOM_RPMPD=y +CONFIG_QCOM_RPMHPD=y From 00ee01362764ab8a30c8ce20ae312af9f0bdccf5 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Wed, 23 May 2018 11:28:21 +0530 Subject: [PATCH 04/35] defconfig updates for sdm845 --- arch/arm64/configs/defconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 6cb98ded4410b0..da176dcaf0e0b0 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -729,3 +729,10 @@ CONFIG_RPMSG_QCOM_GLINK_RPM=y CONFIG_RPMSG_QCOM_GLINK_SMEM=y CONFIG_QCOM_RPMPD=y CONFIG_QCOM_RPMHPD=y +CONFIG_PINCTRL_SDM845=y +CONFIG_SDM_GCC_845=y +CONFIG_QCOM_GENI_SE=y +CONFIG_QCOM_RPMH=y +CONFIG_SERIAL_QCOM_GENI=y +CONFIG_SERIAL_QCOM_GENI_CONSOLE=y +CONFIG_QCOM_COMMAND_DB=y From 30aa5241a41f5fdb03dfa1e8a4cff7d4c1512bce Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Wed, 23 May 2018 11:29:41 +0530 Subject: [PATCH 05/35] Serial DTS node --- arch/arm64/boot/dts/qcom/sdm845-mtp.dts | 41 +++++++++++++++++++++++++ arch/arm64/boot/dts/qcom/sdm845.dtsi | 39 +++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm845-mtp.dts b/arch/arm64/boot/dts/qcom/sdm845-mtp.dts index 979ab49913f1c8..17b2fb0e9513b5 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-mtp.dts +++ b/arch/arm64/boot/dts/qcom/sdm845-mtp.dts @@ -12,4 +12,45 @@ / { model = "Qualcomm Technologies, Inc. SDM845 MTP"; compatible = "qcom,sdm845-mtp"; + + aliases { + serial0 = &uart2; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; +}; + +&soc { + geniqup@ac0000 { + status = "okay"; + + serial@a84000 { + status = "okay"; + }; + }; + + pinctrl@3400000 { + qup-uart2-default { + pinconf_tx { + pins = "gpio4"; + drive-strength = <2>; + bias-disable; + }; + + pinconf_rx { + pins = "gpio5"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + qup-uart2-sleep { + pinconf { + pins = "gpio4", "gpio5"; + bias-pull-down; + }; + }; + }; }; diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi index cdaabeb3c9950e..d07f2962122ded 100644 --- a/arch/arm64/boot/dts/qcom/sdm845.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi @@ -6,6 +6,7 @@ */ #include +#include / { interrupt-parent = <&intc>; @@ -219,6 +220,20 @@ #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; + + qup_uart2_default: qup-uart2-default { + pinmux { + function = "qup9"; + pins = "gpio4", "gpio5"; + }; + }; + + qup_uart2_sleep: qup-uart2-sleep { + pinmux { + function = "gpio"; + pins = "gpio4", "gpio5"; + }; + }; }; spmi_bus: spmi@c440000 { @@ -323,5 +338,29 @@ status = "disabled"; }; }; + + geniqup@ac0000 { + compatible = "qcom,geni-se-qup"; + reg = <0xac0000 0x6000>; + clock-names = "m-ahb", "s-ahb"; + clocks = <&gcc GCC_QUPV3_WRAP_1_M_AHB_CLK>, + <&gcc GCC_QUPV3_WRAP_1_S_AHB_CLK>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + status = "disabled"; + + uart2: serial@a84000 { + compatible = "qcom,geni-debug-uart"; + reg = <0xa84000 0x4000>; + clock-names = "se"; + clocks = <&gcc GCC_QUPV3_WRAP1_S1_CLK>; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&qup_uart2_default>; + pinctrl-1 = <&qup_uart2_sleep>; + interrupts = ; + status = "disabled"; + }; + }; }; }; From 4fbf29da3f967463a0b9a396df216a961dac04be Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:50 -0600 Subject: [PATCH 06/35] drivers: qcom: rpmh-rsc: add RPMH controller for QCOM SoCs Add controller driver for QCOM SoCs that have hardware based shared resource management. The hardware IP known as RSC (Resource State Coordinator) houses multiple Direct Resource Voter (DRV) for different execution levels. A DRV is a unique voter on the state of a shared resource. A Trigger Control Set (TCS) is a bunch of slots that can house multiple resource state requests, that when triggered will issue those requests through an internal bus to the Resource Power Manager Hardened (RPMH) blocks. These hardware blocks are capable of adjusting clocks, voltages, etc. The resource state request from a DRV are aggregated along with state requests from other processors in the SoC and the aggregate value is applied on the resource. Some important aspects of the RPMH communication - - Requests are with some header information - Multiple requests (upto 16) may be sent through a TCS, at a time - Requests in a TCS are sent in sequence - Requests may be fire-n-forget or completion (response expected) - Multiple TCS from the same DRV may be triggered simultaneously - Cannot send a request if another request for the same addr is in progress from the same DRV - When all the requests from a TCS are complete, an IRQ is raised - The IRQ handler needs to clear the TCS before it is available for reuse - TCS configuration is specific to a DRV - Platform drivers may use DRV from different RSCs to make requests Resource state requests made when CPUs are active are called 'active' state requests. Requests made when all the CPUs are powered down (idle state) are called 'sleep' state requests. They are matched by a corresponding 'wake' state requests which puts the resources back in to previously requested active state before resuming any CPU. TCSes are dedicated for each type of requests. Active mode TCSes (AMC) are used to send requests immediately to the resource, while control TCS are used to provide specific information to the controller. Sleep and Wake TCS send sleep and wake requests, after and before the system halt respectively. Signed-off-by: Lina Iyer --- drivers/soc/qcom/Kconfig | 10 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmh-internal.h | 69 ++++ drivers/soc/qcom/rpmh-rsc.c | 483 ++++++++++++++++++++++++ include/dt-bindings/soc/qcom,rpmh-rsc.h | 14 + include/soc/qcom/tcs.h | 56 +++ 6 files changed, 633 insertions(+) create mode 100644 drivers/soc/qcom/rpmh-internal.h create mode 100644 drivers/soc/qcom/rpmh-rsc.c create mode 100644 include/dt-bindings/soc/qcom,rpmh-rsc.h create mode 100644 include/soc/qcom/tcs.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 9dc02f390ba314..c193f460eb4f34 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -74,6 +74,16 @@ config QCOM_RMTFS_MEM Say y here if you intend to boot the modem remoteproc. +config QCOM_RPMH + bool "Qualcomm RPM-Hardened (RPMH) Communication" + depends on ARCH_QCOM && ARM64 && OF || COMPILE_TEST + help + Support for communication with the hardened-RPM blocks in + Qualcomm Technologies Inc (QTI) SoCs. RPMH communication uses an + internal bus to transmit state requests for shared resources. A set + of hardware components aggregate requests for these resources and + help apply the aggregated state on the resource. + config QCOM_SMEM tristate "Qualcomm Shared Memory Manager (SMEM)" depends on ARCH_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 19dcf957cb3a05..4724917d533b59 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_QCOM_PM) += spm.o obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o qmi_helpers-y += qmi_encdec.o qmi_interface.o obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o +obj-$(CONFIG_QCOM_RPMH) += rpmh-rsc.o obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o obj-$(CONFIG_QCOM_SMEM) += smem.o obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h new file mode 100644 index 00000000000000..cc29176f130343 --- /dev/null +++ b/drivers/soc/qcom/rpmh-internal.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + + +#ifndef __RPM_INTERNAL_H__ +#define __RPM_INTERNAL_H__ + +#include +#include + +#define TCS_TYPE_NR 4 +#define MAX_CMDS_PER_TCS 16 +#define MAX_TCS_PER_TYPE 3 +#define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR) + +struct rsc_drv; + +/** + * struct tcs_group: group of Trigger Command Sets (TCS) to send state requests + * to the controller + * + * @drv: the controller + * @type: type of the TCS in this group - active, sleep, wake + * @mask: mask of the TCSes relative to all the TCSes in the RSC + * @offset: start of the TCS group relative to the TCSes in the RSC + * @num_tcs: number of TCSes in this type + * @ncpt: number of commands in each TCS + * @lock: lock for synchronizing this TCS writes + * @req: requests that are sent from the TCS + */ +struct tcs_group { + struct rsc_drv *drv; + int type; + u32 mask; + u32 offset; + int num_tcs; + int ncpt; + spinlock_t lock; + const struct tcs_request *req[MAX_TCS_PER_TYPE]; +}; + +/** + * struct rsc_drv: the Direct Resource Voter (DRV) of the + * Resource State Coordinator controller (RSC) + * + * @name: controller identifier + * @tcs_base: start address of the TCS registers in this controller + * @id: instance id in the controller (Direct Resource Voter) + * @num_tcs: number of TCSes in this DRV + * @tcs: TCS groups + * @tcs_in_use: s/w state of the TCS + * @lock: synchronize state of the controller + */ +struct rsc_drv { + const char *name; + void __iomem *tcs_base; + int id; + int num_tcs; + struct tcs_group tcs[TCS_TYPE_NR]; + DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR); + spinlock_t lock; +}; + + +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg); + +#endif /* __RPM_INTERNAL_H__ */ diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c new file mode 100644 index 00000000000000..4eeccf75cc1518 --- /dev/null +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s " fmt, KBUILD_MODNAME + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rpmh-internal.h" + +#define RSC_DRV_TCS_OFFSET 672 +#define RSC_DRV_CMD_OFFSET 20 + +/* DRV Configuration Information Register */ +#define DRV_PRNT_CHLD_CONFIG 0x0C +#define DRV_NUM_TCS_MASK 0x3F +#define DRV_NUM_TCS_SHIFT 6 +#define DRV_NCPT_MASK 0x1F +#define DRV_NCPT_SHIFT 27 + +/* Register offsets */ +#define RSC_DRV_IRQ_ENABLE 0x00 +#define RSC_DRV_IRQ_STATUS 0x04 +#define RSC_DRV_IRQ_CLEAR 0x08 +#define RSC_DRV_CMD_WAIT_FOR_CMPL 0x10 +#define RSC_DRV_CONTROL 0x14 +#define RSC_DRV_STATUS 0x18 +#define RSC_DRV_CMD_ENABLE 0x1C +#define RSC_DRV_CMD_MSGID 0x30 +#define RSC_DRV_CMD_ADDR 0x34 +#define RSC_DRV_CMD_DATA 0x38 +#define RSC_DRV_CMD_STATUS 0x3C +#define RSC_DRV_CMD_RESP_DATA 0x40 + +#define TCS_AMC_MODE_ENABLE BIT(16) +#define TCS_AMC_MODE_TRIGGER BIT(24) + +/* TCS CMD register bit mask */ +#define CMD_MSGID_LEN 8 +#define CMD_MSGID_RESP_REQ BIT(8) +#define CMD_MSGID_WRITE BIT(16) +#define CMD_STATUS_ISSUED BIT(8) +#define CMD_STATUS_COMPL BIT(16) + +static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id) +{ + return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id + + RSC_DRV_CMD_OFFSET * cmd_id); +} + +static void write_tcs_cmd(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id, + u32 data) +{ + writel_relaxed(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id + + RSC_DRV_CMD_OFFSET * cmd_id); +} + +static void write_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, u32 data) +{ + writel_relaxed(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id); +} + +static void write_tcs_reg_sync(struct rsc_drv *drv, int reg, int tcs_id, + u32 data) +{ + writel(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id); + for (;;) { + if (data == readl(drv->tcs_base + reg + + RSC_DRV_TCS_OFFSET * tcs_id)) + break; + udelay(1); + } +} + +static bool tcs_is_free(struct rsc_drv *drv, int tcs_id) +{ + return !test_bit(tcs_id, drv->tcs_in_use) && + read_tcs_reg(drv, RSC_DRV_STATUS, tcs_id, 0); +} + +static struct tcs_group *get_tcs_of_type(struct rsc_drv *drv, int type) +{ + return &drv->tcs[type]; +} + +static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, + const struct tcs_request *msg) +{ + int type; + + switch (msg->state) { + case RPMH_ACTIVE_ONLY_STATE: + type = ACTIVE_TCS; + break; + default: + return ERR_PTR(-EINVAL); + } + + return get_tcs_of_type(drv, type); +} + +static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv, + int tcs_id) +{ + struct tcs_group *tcs; + int i; + + for (i = 0; i < drv->num_tcs; i++) { + tcs = &drv->tcs[i]; + if (tcs->mask & BIT(tcs_id)) + return tcs->req[tcs_id - tcs->offset]; + } + + return NULL; +} + +/** + * tcs_tx_done: TX Done interrupt handler + */ +static irqreturn_t tcs_tx_done(int irq, void *p) +{ + struct rsc_drv *drv = p; + int i, j; + unsigned long irq_status; + const struct tcs_request *req; + struct tcs_cmd *cmd; + + irq_status = read_tcs_reg(drv, RSC_DRV_IRQ_STATUS, 0, 0); + + for_each_set_bit(i, &irq_status, BITS_PER_LONG) { + req = get_req_from_tcs(drv, i); + if (!req) { + WARN_ON(1); + goto skip; + } + + for (j = 0; j < req->num_cmds; j++) { + u32 sts; + + cmd = &req->cmds[j]; + sts = read_tcs_reg(drv, RSC_DRV_CMD_STATUS, i, j); + if (!(sts & CMD_STATUS_ISSUED) || + ((req->wait_for_compl || cmd->wait) && + !(sts & CMD_STATUS_COMPL))) { + pr_err("Incomplete request: %s: addr=%#x data=%#x", + drv->name, cmd->addr, cmd->data); + } + } +skip: + /* Reclaim the TCS */ + write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, i, 0); + write_tcs_reg(drv, RSC_DRV_IRQ_CLEAR, 0, BIT(i)); + spin_lock(&drv->lock); + clear_bit(i, drv->tcs_in_use); + spin_unlock(&drv->lock); + } + + return IRQ_HANDLED; +} + +static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id, + const struct tcs_request *msg) +{ + u32 msgid, cmd_msgid; + u32 cmd_enable = 0; + u32 cmd_complete; + struct tcs_cmd *cmd; + int i, j; + + cmd_msgid = CMD_MSGID_LEN; + cmd_msgid |= msg->wait_for_compl ? CMD_MSGID_RESP_REQ : 0; + cmd_msgid |= CMD_MSGID_WRITE; + + cmd_complete = read_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, tcs_id, 0); + + for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) { + cmd = &msg->cmds[i]; + cmd_enable |= BIT(j); + cmd_complete |= cmd->wait << j; + msgid = cmd_msgid; + msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0; + write_tcs_cmd(drv, RSC_DRV_CMD_MSGID, tcs_id, j, msgid); + write_tcs_cmd(drv, RSC_DRV_CMD_ADDR, tcs_id, j, cmd->addr); + write_tcs_cmd(drv, RSC_DRV_CMD_DATA, tcs_id, j, cmd->data); + } + + write_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, tcs_id, cmd_complete); + cmd_enable |= read_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, 0); + write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, cmd_enable); +} + +static void __tcs_trigger(struct rsc_drv *drv, int tcs_id) +{ + u32 enable; + + /* + * HW req: Clear the DRV_CONTROL and enable TCS again + * While clearing ensure that the AMC mode trigger is cleared + * and then the mode enable is cleared. + */ + enable = read_tcs_reg(drv, RSC_DRV_CONTROL, tcs_id, 0); + enable &= ~TCS_AMC_MODE_TRIGGER; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable); + enable &= ~TCS_AMC_MODE_ENABLE; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable); + + /* Enable the AMC mode on the TCS and then trigger the TCS */ + enable = TCS_AMC_MODE_ENABLE; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable); + enable |= TCS_AMC_MODE_TRIGGER; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable); +} + +static int check_for_req_inflight(struct rsc_drv *drv, struct tcs_group *tcs, + const struct tcs_request *msg) +{ + unsigned long curr_enabled; + u32 addr; + int i, j, k; + int tcs_id = tcs->offset; + + for (i = 0; i < tcs->num_tcs; i++, tcs_id++) { + if (tcs_is_free(drv, tcs_id)) + continue; + + curr_enabled = read_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, 0); + + for_each_set_bit(j, &curr_enabled, MAX_CMDS_PER_TCS) { + addr = read_tcs_reg(drv, RSC_DRV_CMD_ADDR, tcs_id, j); + for (k = 0; k < msg->num_cmds; k++) { + if (addr == msg->cmds[k].addr) + return -EBUSY; + } + } + } + + return 0; +} + +static int find_free_tcs(struct tcs_group *tcs) +{ + int i; + + for (i = 0; i < tcs->num_tcs; i++) { + if (tcs_is_free(tcs->drv, tcs->offset + i)) + return tcs->offset + i; + } + + return -EBUSY; +} + +static int tcs_write(struct rsc_drv *drv, const struct tcs_request *msg) +{ + struct tcs_group *tcs; + int tcs_id; + unsigned long flags; + int ret; + + tcs = get_tcs_for_msg(drv, msg); + if (IS_ERR(tcs)) + return PTR_ERR(tcs); + + spin_lock_irqsave(&tcs->lock, flags); + spin_lock(&drv->lock); + /* + * The h/w does not like if we send a request to the same address, + * when one is already in-flight or being processed. + */ + ret = check_for_req_inflight(drv, tcs, msg); + if (ret) { + spin_unlock(&drv->lock); + goto done_write; + } + + tcs_id = find_free_tcs(tcs); + if (tcs_id < 0) { + ret = tcs_id; + spin_unlock(&drv->lock); + goto done_write; + } + + tcs->req[tcs_id - tcs->offset] = msg; + set_bit(tcs_id, drv->tcs_in_use); + spin_unlock(&drv->lock); + + __tcs_buffer_write(drv, tcs_id, 0, msg); + __tcs_trigger(drv, tcs_id); + +done_write: + spin_unlock_irqrestore(&tcs->lock, flags); + return ret; +} + +/** + * rpmh_rsc_send_data: Validate the incoming message and write to the + * appropriate TCS block. + * + * @drv: the controller + * @msg: the data to be sent + * + * Return: 0 on success, -EINVAL on error. + * Note: This call blocks until a valid data is written to the TCS. + */ +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg) +{ + int ret; + + if (!msg || !msg->cmds || !msg->num_cmds || + msg->num_cmds > MAX_RPMH_PAYLOAD) { + WARN_ON(1); + return -EINVAL; + } + + do { + ret = tcs_write(drv, msg); + if (ret == -EBUSY) { + pr_info_ratelimited("TCS Busy, retrying RPMH message send: addr=%#x\n", + msg->cmds[0].addr); + udelay(10); + } + } while (ret == -EBUSY); + + return ret; +} +EXPORT_SYMBOL(rpmh_rsc_send_data); + +static int rpmh_probe_tcs_config(struct platform_device *pdev, + struct rsc_drv *drv) +{ + struct tcs_type_config { + u32 type; + u32 n; + } tcs_cfg[TCS_TYPE_NR] = { { 0 } }; + struct device_node *dn = pdev->dev.of_node; + u32 config, max_tcs, ncpt, offset; + int i, ret, n, st = 0; + struct tcs_group *tcs; + struct resource *res; + void __iomem *base; + char drv_id[10] = {0}; + + snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, drv_id); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + ret = of_property_read_u32(dn, "qcom,tcs-offset", &offset); + if (ret) + return ret; + drv->tcs_base = base + offset; + + config = readl_relaxed(base + DRV_PRNT_CHLD_CONFIG); + + max_tcs = config; + max_tcs &= DRV_NUM_TCS_MASK << (DRV_NUM_TCS_SHIFT * drv->id); + max_tcs = max_tcs >> (DRV_NUM_TCS_SHIFT * drv->id); + + ncpt = config & (DRV_NCPT_MASK << DRV_NCPT_SHIFT); + ncpt = ncpt >> DRV_NCPT_SHIFT; + + n = of_property_count_u32_elems(dn, "qcom,tcs-config"); + if (n != 2 * TCS_TYPE_NR) + return -EINVAL; + + for (i = 0; i < TCS_TYPE_NR; i++) { + ret = of_property_read_u32_index(dn, "qcom,tcs-config", + i * 2, &tcs_cfg[i].type); + if (ret) + return ret; + if (tcs_cfg[i].type >= TCS_TYPE_NR) + return -EINVAL; + + ret = of_property_read_u32_index(dn, "qcom,tcs-config", + i * 2 + 1, &tcs_cfg[i].n); + if (ret) + return ret; + if (tcs_cfg[i].n > MAX_TCS_PER_TYPE) + return -EINVAL; + } + + for (i = 0; i < TCS_TYPE_NR; i++) { + tcs = &drv->tcs[tcs_cfg[i].type]; + if (tcs->drv) + return -EINVAL; + tcs->drv = drv; + tcs->type = tcs_cfg[i].type; + tcs->num_tcs = tcs_cfg[i].n; + tcs->ncpt = ncpt; + spin_lock_init(&tcs->lock); + + if (!tcs->num_tcs || tcs->type == CONTROL_TCS) + continue; + + if (st + tcs->num_tcs > max_tcs || + st + tcs->num_tcs >= BITS_PER_BYTE * sizeof(tcs->mask)) + return -EINVAL; + + tcs->mask = ((1 << tcs->num_tcs) - 1) << st; + tcs->offset = st; + st += tcs->num_tcs; + } + + drv->num_tcs = st; + + return 0; +} + +static int rpmh_rsc_probe(struct platform_device *pdev) +{ + struct device_node *dn = pdev->dev.of_node; + struct rsc_drv *drv; + int ret, irq; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + ret = of_property_read_u32(dn, "qcom,drv-id", &drv->id); + if (ret) + return ret; + + drv->name = of_get_property(dn, "label", NULL); + if (!drv->name) + drv->name = dev_name(&pdev->dev); + + ret = rpmh_probe_tcs_config(pdev, drv); + if (ret) + return ret; + + spin_lock_init(&drv->lock); + bitmap_zero(drv->tcs_in_use, MAX_TCS_NR); + + irq = platform_get_irq(pdev, drv->id); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, tcs_tx_done, + IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, + drv->name, drv); + if (ret) + return ret; + + /* Enable the active TCS to send requests immediately */ + write_tcs_reg(drv, RSC_DRV_IRQ_ENABLE, 0, drv->tcs[ACTIVE_TCS].mask); + + return devm_of_platform_populate(&pdev->dev); +} + +static const struct of_device_id rpmh_drv_match[] = { + { .compatible = "qcom,rpmh-rsc", }, + { } +}; + +static struct platform_driver rpmh_driver = { + .probe = rpmh_rsc_probe, + .driver = { + .name = "rpmh", + .of_match_table = rpmh_drv_match, + }, +}; + +static int __init rpmh_driver_init(void) +{ + return platform_driver_register(&rpmh_driver); +} +arch_initcall(rpmh_driver_init); diff --git a/include/dt-bindings/soc/qcom,rpmh-rsc.h b/include/dt-bindings/soc/qcom,rpmh-rsc.h new file mode 100644 index 00000000000000..868f998ea998f5 --- /dev/null +++ b/include/dt-bindings/soc/qcom,rpmh-rsc.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __DT_QCOM_RPMH_RSC_H__ +#define __DT_QCOM_RPMH_RSC_H__ + +#define SLEEP_TCS 0 +#define WAKE_TCS 1 +#define ACTIVE_TCS 2 +#define CONTROL_TCS 3 + +#endif /* __DT_QCOM_RPMH_RSC_H__ */ diff --git a/include/soc/qcom/tcs.h b/include/soc/qcom/tcs.h new file mode 100644 index 00000000000000..262876a59e86dd --- /dev/null +++ b/include/soc/qcom/tcs.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_QCOM_TCS_H__ +#define __SOC_QCOM_TCS_H__ + +#define MAX_RPMH_PAYLOAD 16 + +/** + * rpmh_state: state for the request + * + * RPMH_SLEEP_STATE: State of the resource when the processor subsystem + * is powered down. There is no client using the + * resource actively. + * RPMH_WAKE_ONLY_STATE: Resume resource state to the value previously + * requested before the processor was powered down. + * RPMH_ACTIVE_ONLY_STATE: Active or AMC mode requests. Resource state + * is aggregated immediately. + */ +enum rpmh_state { + RPMH_SLEEP_STATE, + RPMH_WAKE_ONLY_STATE, + RPMH_ACTIVE_ONLY_STATE, +}; + +/** + * struct tcs_cmd: an individual request to RPMH. + * + * @addr: the address of the resource slv_id:18:16 | offset:0:15 + * @data: the resource state request + * @wait: wait for this request to be complete before sending the next + */ +struct tcs_cmd { + u32 addr; + u32 data; + u32 wait; +}; + +/** + * struct tcs_request: A set of tcs_cmds sent together in a TCS + * + * @state: state for the request. + * @wait_for_compl: wait until we get a response from the h/w accelerator + * @num_cmds: the number of @cmds in this request + * @cmds: an array of tcs_cmds + */ +struct tcs_request { + enum rpmh_state state; + u32 wait_for_compl; + u32 num_cmds; + struct tcs_cmd *cmds; +}; + +#endif /* __SOC_QCOM_TCS_H__ */ From cd73d065680237aba8b8975a36f4768e9716f0ae Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:51 -0600 Subject: [PATCH 07/35] dt-bindings: introduce RPMH RSC bindings for Qualcomm SoCs Add device binding documentation for Qualcomm Technology Inc's RPMH RSC driver. The driver is used for communicating resource state requests for shared resources. Cc: devicetree@vger.kernel.org Signed-off-by: Lina Iyer Reviewed-by: Rob Herring --- .../devicetree/bindings/soc/qcom/rpmh-rsc.txt | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/rpmh-rsc.txt diff --git a/Documentation/devicetree/bindings/soc/qcom/rpmh-rsc.txt b/Documentation/devicetree/bindings/soc/qcom/rpmh-rsc.txt new file mode 100644 index 00000000000000..e15c100f5c9242 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/rpmh-rsc.txt @@ -0,0 +1,137 @@ +RPMH RSC: +------------ + +Resource Power Manager Hardened (RPMH) is the mechanism for communicating with +the hardened resource accelerators on Qualcomm SoCs. Requests to the resources +can be written to the Trigger Command Set (TCS) registers and using a (addr, +val) pair and triggered. Messages in the TCS are then sent in sequence over an +internal bus. + +The hardware block (Direct Resource Voter or DRV) is a part of the h/w entity +(Resource State Coordinator a.k.a RSC) that can handle multiple sleep and +active/wake resource requests. Multiple such DRVs can exist in a SoC and can +be written to from Linux. The structure of each DRV follows the same template +with a few variations that are captured by the properties here. + +A TCS may be triggered from Linux or triggered by the F/W after all the CPUs +have powered off to facilitate idle power saving. TCS could be classified as - + + SLEEP /* Triggered by F/W */ + WAKE /* Triggered by F/W */ + ACTIVE /* Triggered by Linux */ + CONTROL /* Triggered by F/W */ + +The order in which they are described in the DT, should match the hardware +configuration. + +Requests can be made for the state of a resource, when the subsystem is active +or idle. When all subsystems like Modem, GPU, CPU are idle, the resource state +will be an aggregate of the sleep votes from each of those subsystems. Clients +may request a sleep value for their shared resources in addition to the active +mode requests. + +Properties: + +- compatible: + Usage: required + Value type: + Definition: Should be "qcom,rpmh-rsc". + +- reg: + Usage: required + Value type: + Definition: The first register specifies the base address of the + DRV(s). The number of DRVs in the dependent on the RSC. + The tcs-offset specifies the start address of the + TCS in the DRVs. + +- reg-names: + Usage: required + Value type: + Definition: Maps the register specified in the reg property. Must be + "drv-0", "drv-1", "drv-2" etc and "tcs-offset". The + +- interrupts: + Usage: required + Value type: + Definition: The interrupt that trips when a message complete/response + is received for this DRV from the accelerators. + +- qcom,drv-id: + Usage: required + Value type: + Definition: The id of the DRV in the RSC block that will be used by + this controller. + +- qcom,tcs-config: + Usage: required + Value type: + Definition: The tuple defining the configuration of TCS. + Must have 2 cells which describe each TCS type. + . + The order of the TCS must match the hardware + configuration. + - Cell #1 (TCS Type): TCS types to be specified - + SLEEP_TCS + WAKE_TCS + ACTIVE_TCS + CONTROL_TCS + - Cell #2 (Number of TCS): + +- label: + Usage: optional + Value type: + Definition: Name for the RSC. The name would be used in trace logs. + +Drivers that want to use the RSC to communicate with RPMH must specify their +bindings as child nodes of the RSC controllers they wish to communicate with. + +Example 1: + +For a TCS whose RSC base address is is 0x179C0000 and is at a DRV id of 2, the +register offsets for DRV2 start at 0D00, the register calculations are like +this - +DRV0: 0x179C0000 +DRV2: 0x179C0000 + 0x10000 = 0x179D0000 +DRV2: 0x179C0000 + 0x10000 * 2 = 0x179E0000 +TCS-OFFSET: 0xD00 + + apps_rsc: rsc@179c0000 { + label = "apps_rsc"; + compatible = "qcom,rpmh-rsc"; + reg = <0x179c0000 0x10000>, + <0x179d0000 0x10000>, + <0x179e0000 0x10000>; + reg-names = "drv-0", "drv-1", "drv-2"; + interrupts = , + , + ; + qcom,tcs-offset = <0xd00>; + qcom,drv-id = <2>; + qcom,tcs-config = , + , + , + ; + }; + +Example 2: + +For a TCS whose RSC base address is 0xAF20000 and is at DRV id of 0, the +register offsets for DRV0 start at 01C00, the register calculations are like +this - +DRV0: 0xAF20000 +TCS-OFFSET: 0x1C00 + + disp_rsc: rsc@af20000 { + label = "disp_rsc"; + compatible = "qcom,rpmh-rsc"; + reg = <0xaf20000 0x10000>; + reg-names = "drv-0"; + interrupts = ; + qcom,tcs-offset = <0x1c00>; + qcom,drv-id = <0>; + qcom,tcs-config = , + , + , + ; + }; From 893d06e5caa9c1297b1d24af6dd32ecabf563646 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:52 -0600 Subject: [PATCH 08/35] drivers: qcom: rpmh-rsc: log RPMH requests in FTRACE Log sent RPMH requests and interrupt responses in FTRACE. Cc: Steven Rostedt Signed-off-by: Lina Iyer --- drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmh-rsc.c | 11 ++++- drivers/soc/qcom/trace-rpmh.h | 82 +++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 drivers/soc/qcom/trace-rpmh.h diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 4724917d533b59..fa901719b725af 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +CFLAGS_rpmh-rsc.o := -I$(src) obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 4eeccf75cc1518..0a8cec9d165169 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -24,6 +24,9 @@ #include "rpmh-internal.h" +#define CREATE_TRACE_POINTS +#include "trace-rpmh.h" + #define RSC_DRV_TCS_OFFSET 672 #define RSC_DRV_CMD_OFFSET 20 @@ -136,7 +139,7 @@ static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv, static irqreturn_t tcs_tx_done(int irq, void *p) { struct rsc_drv *drv = p; - int i, j; + int i, j, err; unsigned long irq_status; const struct tcs_request *req; struct tcs_cmd *cmd; @@ -150,6 +153,7 @@ static irqreturn_t tcs_tx_done(int irq, void *p) goto skip; } + err = 0; for (j = 0; j < req->num_cmds; j++) { u32 sts; @@ -160,8 +164,11 @@ static irqreturn_t tcs_tx_done(int irq, void *p) !(sts & CMD_STATUS_COMPL))) { pr_err("Incomplete request: %s: addr=%#x data=%#x", drv->name, cmd->addr, cmd->data); + err = -EIO; } } + + trace_rpmh_tx_done(drv, i, req, err); skip: /* Reclaim the TCS */ write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, i, 0); @@ -195,9 +202,11 @@ static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id, cmd_complete |= cmd->wait << j; msgid = cmd_msgid; msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0; + write_tcs_cmd(drv, RSC_DRV_CMD_MSGID, tcs_id, j, msgid); write_tcs_cmd(drv, RSC_DRV_CMD_ADDR, tcs_id, j, cmd->addr); write_tcs_cmd(drv, RSC_DRV_CMD_DATA, tcs_id, j, cmd->data); + trace_rpmh_send_msg(drv, tcs_id, j, msgid, cmd); } write_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, tcs_id, cmd_complete); diff --git a/drivers/soc/qcom/trace-rpmh.h b/drivers/soc/qcom/trace-rpmh.h new file mode 100644 index 00000000000000..feb0cb455e3748 --- /dev/null +++ b/drivers/soc/qcom/trace-rpmh.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#if !defined(_TRACE_RPMH_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_RPMH_H + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM rpmh + +#include +#include "rpmh-internal.h" + +TRACE_EVENT(rpmh_tx_done, + + TP_PROTO(struct rsc_drv *d, int m, const struct tcs_request *r, int e), + + TP_ARGS(d, m, r, e), + + TP_STRUCT__entry( + __string(name, d->name) + __field(int, m) + __field(u32, addr) + __field(u32, data) + __field(int, err) + ), + + TP_fast_assign( + __assign_str(name, d->name); + __entry->m = m; + __entry->addr = r->cmds[0].addr; + __entry->data = r->cmds[0].data; + __entry->err = e; + ), + + TP_printk("%s: ack: tcs-m: %d addr: %#x data: %#x errno: %d", + __get_str(name), __entry->m, __entry->addr, __entry->data, + __entry->err) +); + +TRACE_EVENT(rpmh_send_msg, + + TP_PROTO(struct rsc_drv *d, int m, int n, u32 h, + const struct tcs_cmd *c), + + TP_ARGS(d, m, n, h, c), + + TP_STRUCT__entry( + __string(name, d->name) + __field(int, m) + __field(int, n) + __field(u32, hdr) + __field(u32, addr) + __field(u32, data) + __field(bool, wait) + ), + + TP_fast_assign( + __assign_str(name, d->name); + __entry->m = m; + __entry->n = n; + __entry->hdr = h; + __entry->addr = c->addr; + __entry->data = c->data; + __entry->wait = c->wait; + ), + + TP_printk("%s: send-msg: tcs(m): %d cmd(n): %d msgid: %#x addr: %#x data: %#x complete: %d", + __get_str(name), __entry->m, __entry->n, __entry->hdr, + __entry->addr, __entry->data, __entry->wait) +); + +#endif /* _TRACE_RPMH_H */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace-rpmh + +#include From 720e55798f754dcbce14fb23214ee6dc10aa2b91 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:53 -0600 Subject: [PATCH 09/35] drivers: qcom: rpmh: add RPMH helper functions Sending RPMH requests and waiting for response from the controller through a callback is common functionality across all platform drivers. To simplify drivers, add a library functions to create RPMH client and send resource state requests. rpmh_write() is a synchronous blocking call that can be used to send active state requests. Signed-off-by: Lina Iyer --- drivers/soc/qcom/Makefile | 4 +- drivers/soc/qcom/rpmh-internal.h | 6 ++ drivers/soc/qcom/rpmh-rsc.c | 8 ++ drivers/soc/qcom/rpmh.c | 176 +++++++++++++++++++++++++++++++ include/soc/qcom/rpmh.h | 25 +++++ 5 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 drivers/soc/qcom/rpmh.c create mode 100644 include/soc/qcom/rpmh.h diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index fa901719b725af..03d4c834641813 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -9,7 +9,9 @@ obj-$(CONFIG_QCOM_PM) += spm.o obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o qmi_helpers-y += qmi_encdec.o qmi_interface.o obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o -obj-$(CONFIG_QCOM_RPMH) += rpmh-rsc.o +obj-$(CONFIG_QCOM_RPMH) += qcom_rpmh.o +qcom_rpmh-y += rpmh-rsc.o +qcom_rpmh-y += rpmh.o obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o obj-$(CONFIG_QCOM_SMEM) += smem.o obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index cc29176f130343..d9a21726e5680f 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -14,6 +14,7 @@ #define MAX_CMDS_PER_TCS 16 #define MAX_TCS_PER_TYPE 3 #define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR) +#define RPMH_MAX_CTRLR 2 struct rsc_drv; @@ -52,6 +53,7 @@ struct tcs_group { * @tcs: TCS groups * @tcs_in_use: s/w state of the TCS * @lock: synchronize state of the controller + * @list: element in list of drv */ struct rsc_drv { const char *name; @@ -61,9 +63,13 @@ struct rsc_drv { struct tcs_group tcs[TCS_TYPE_NR]; DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR); spinlock_t lock; + struct list_head list; }; +extern struct list_head rsc_drv_list; int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg); +void rpmh_tx_done(const struct tcs_request *msg, int r); + #endif /* __RPM_INTERNAL_H__ */ diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 0a8cec9d165169..c0edf385014763 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -61,6 +61,8 @@ #define CMD_STATUS_ISSUED BIT(8) #define CMD_STATUS_COMPL BIT(16) +LIST_HEAD(rsc_drv_list); + static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id) { return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id + @@ -176,6 +178,8 @@ static irqreturn_t tcs_tx_done(int irq, void *p) spin_lock(&drv->lock); clear_bit(i, drv->tcs_in_use); spin_unlock(&drv->lock); + if (req) + rpmh_tx_done(req, err); } return IRQ_HANDLED; @@ -469,6 +473,10 @@ static int rpmh_rsc_probe(struct platform_device *pdev) /* Enable the active TCS to send requests immediately */ write_tcs_reg(drv, RSC_DRV_IRQ_ENABLE, 0, drv->tcs[ACTIVE_TCS].mask); + INIT_LIST_HEAD(&drv->list); + list_add(&drv->list, &rsc_drv_list); + dev_set_drvdata(&pdev->dev, drv); + return devm_of_platform_populate(&pdev->dev); } diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c new file mode 100644 index 00000000000000..74bb82339b0157 --- /dev/null +++ b/drivers/soc/qcom/rpmh.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rpmh-internal.h" + +#define RPMH_TIMEOUT_MS msecs_to_jiffies(10000) + +#define DEFINE_RPMH_MSG_ONSTACK(dev, s, q, name) \ + struct rpmh_request name = { \ + .msg = { \ + .state = s, \ + .cmds = name.cmd, \ + .num_cmds = 0, \ + .wait_for_compl = true, \ + }, \ + .cmd = { { 0 } }, \ + .completion = q, \ + .dev = dev, \ + } + +/** + * struct rpmh_request: the message to be sent to rpmh-rsc + * + * @msg: the request + * @cmd: the payload that will be part of the @msg + * @completion: triggered when request is done + * @dev: the device making the request + * @err: err return from the controller + */ +struct rpmh_request { + struct tcs_request msg; + struct tcs_cmd cmd[MAX_RPMH_PAYLOAD]; + struct completion *completion; + const struct device *dev; + int err; +}; + +/** + * struct rpmh_ctrlr: our representation of the controller + * + * @drv: the controller instance + */ +struct rpmh_ctrlr { + struct rsc_drv *drv; +}; + +static struct rpmh_ctrlr rpmh_rsc[RPMH_MAX_CTRLR]; +static DEFINE_SPINLOCK(rpmh_rsc_lock); + +static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct device *dev) +{ + int i; + struct rsc_drv *p, *drv = dev_get_drvdata(dev->parent); + struct rpmh_ctrlr *ctrlr = ERR_PTR(-EINVAL); + unsigned long flags; + + if (!drv) + return ctrlr; + + for (i = 0; i < RPMH_MAX_CTRLR; i++) { + if (rpmh_rsc[i].drv == drv) { + ctrlr = &rpmh_rsc[i]; + return ctrlr; + } + } + + spin_lock_irqsave(&rpmh_rsc_lock, flags); + list_for_each_entry(p, &rsc_drv_list, list) { + if (drv == p) { + for (i = 0; i < RPMH_MAX_CTRLR; i++) { + if (!rpmh_rsc[i].drv) + break; + } + if (i == RPMH_MAX_CTRLR) { + ctrlr = ERR_PTR(-ENOMEM); + break; + } + rpmh_rsc[i].drv = drv; + ctrlr = &rpmh_rsc[i]; + break; + } + } + spin_unlock_irqrestore(&rpmh_rsc_lock, flags); + + return ctrlr; +} + +void rpmh_tx_done(const struct tcs_request *msg, int r) +{ + struct rpmh_request *rpm_msg = container_of(msg, struct rpmh_request, + msg); + struct completion *compl = rpm_msg->completion; + + rpm_msg->err = r; + + if (r) + dev_err(rpm_msg->dev, "RPMH TX fail in msg addr=%#x, err=%d\n", + rpm_msg->msg.cmds[0].addr, r); + + /* Signal the blocking thread we are done */ + if (compl) + complete(compl); +} +EXPORT_SYMBOL(rpmh_tx_done); + +/** + * __rpmh_write: send the RPMH request + * + * @dev: The device making the request + * @state: Active/Sleep request type + * @rpm_msg: The data that needs to be sent (cmds). + */ +static int __rpmh_write(const struct device *dev, enum rpmh_state state, + struct rpmh_request *rpm_msg) +{ + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + + if (IS_ERR(ctrlr)) + return PTR_ERR(ctrlr); + + rpm_msg->msg.state = state; + + if (state != RPMH_ACTIVE_ONLY_STATE) + return -EINVAL; + + WARN_ON(irqs_disabled()); + + return rpmh_rsc_send_data(ctrlr->drv, &rpm_msg->msg); +} + +/** + * rpmh_write: Write a set of RPMH commands and block until response + * + * @rc: The RPMH handle got from rpmh_get_client + * @state: Active/sleep set + * @cmd: The payload data + * @n: The number of elements in @cmd + * + * May sleep. Do not call from atomic contexts. + */ +int rpmh_write(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + DECLARE_COMPLETION_ONSTACK(compl); + DEFINE_RPMH_MSG_ONSTACK(dev, state, &compl, rpm_msg); + int ret; + + if (!cmd || !n || n > MAX_RPMH_PAYLOAD) + return -EINVAL; + + memcpy(rpm_msg.cmd, cmd, n * sizeof(*cmd)); + rpm_msg.msg.num_cmds = n; + + ret = __rpmh_write(dev, state, &rpm_msg); + if (ret) + return ret; + + ret = wait_for_completion_timeout(&compl, RPMH_TIMEOUT_MS); + return (ret > 0) ? 0 : -ETIMEDOUT; +} +EXPORT_SYMBOL(rpmh_write); diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h new file mode 100644 index 00000000000000..c1d0f902bd7144 --- /dev/null +++ b/include/soc/qcom/rpmh.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_QCOM_RPMH_H__ +#define __SOC_QCOM_RPMH_H__ + +#include +#include + + +#if IS_ENABLED(CONFIG_QCOM_RPMH) +int rpmh_write(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n); + +#else + +static inline int rpmh_write(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ return -ENODEV; } + +#endif /* CONFIG_QCOM_RPMH */ + +#endif /* __SOC_QCOM_RPMH_H__ */ From b7f25b298b215700534eec423f65a6304ed2b9f1 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:54 -0600 Subject: [PATCH 10/35] drivers: qcom: rpmh-rsc: write sleep/wake requests to TCS Sleep and wake requests are sent when the application processor subsystem of the SoC is entering deep sleep states like in suspend. These requests help lower the system power requirements when the resources are not in use. Sleep and wake requests are written to the TCS slots but are not triggered at the time of writing. The TCS are triggered by the firmware after the last of the CPUs has executed its WFI. Since these requests may come in different batches of requests, it is the job of this controller driver to find and arrange the requests into the available TCSes. Signed-off-by: Lina Iyer Reviewed-by: Evan Green --- drivers/soc/qcom/rpmh-internal.h | 8 ++ drivers/soc/qcom/rpmh-rsc.c | 122 +++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index d9a21726e5680f..6e19fe458c31ea 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -14,6 +14,7 @@ #define MAX_CMDS_PER_TCS 16 #define MAX_TCS_PER_TYPE 3 #define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR) +#define MAX_TCS_SLOTS (MAX_CMDS_PER_TCS * MAX_TCS_PER_TYPE) #define RPMH_MAX_CTRLR 2 struct rsc_drv; @@ -30,6 +31,8 @@ struct rsc_drv; * @ncpt: number of commands in each TCS * @lock: lock for synchronizing this TCS writes * @req: requests that are sent from the TCS + * @cmd_cache: flattened cache of cmds in sleep/wake TCS + * @slots: indicates which of @cmd_addr are occupied */ struct tcs_group { struct rsc_drv *drv; @@ -40,6 +43,8 @@ struct tcs_group { int ncpt; spinlock_t lock; const struct tcs_request *req[MAX_TCS_PER_TYPE]; + u32 *cmd_cache; + DECLARE_BITMAP(slots, MAX_TCS_SLOTS); }; /** @@ -69,6 +74,9 @@ struct rsc_drv { extern struct list_head rsc_drv_list; int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg); +int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, + const struct tcs_request *msg); +int rpmh_rsc_invalidate(struct rsc_drv *drv); void rpmh_tx_done(const struct tcs_request *msg, int r); diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index c0edf385014763..b5894b001ae107 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -113,6 +113,12 @@ static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, case RPMH_ACTIVE_ONLY_STATE: type = ACTIVE_TCS; break; + case RPMH_WAKE_ONLY_STATE: + type = WAKE_TCS; + break; + case RPMH_SLEEP_STATE: + type = SLEEP_TCS; + break; default: return ERR_PTR(-EINVAL); } @@ -353,6 +359,109 @@ int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg) } EXPORT_SYMBOL(rpmh_rsc_send_data); +static int find_match(const struct tcs_group *tcs, const struct tcs_cmd *cmd, + int len) +{ + int i, j; + + /* Check for already cached commands */ + for_each_set_bit(i, tcs->slots, MAX_TCS_SLOTS) { + if (tcs->cmd_cache[i] != cmd[0].addr) + continue; + if (i + len >= MAX_TCS_SLOTS) + goto seq_err; + for (j = 0; j < len; j++) { + if (tcs->cmd_cache[i + j] != cmd[j].addr) + goto seq_err; + } + return i; + } + + return -ENODATA; + +seq_err: + WARN(1, "Message does not match previous sequence.\n"); + return -EINVAL; +} + +static int find_slots(struct tcs_group *tcs, const struct tcs_request *msg, + int *tcs_id, int *cmd_id) +{ + int slot, offset; + int i = 0; + + /* Find if we already have the msg in our TCS */ + slot = find_match(tcs, msg->cmds, msg->num_cmds); + if (slot >= 0) + goto copy_data; + + /* Do over, until we can fit the full payload in a TCS */ + do { + slot = bitmap_find_next_zero_area(tcs->slots, MAX_TCS_SLOTS, + i, msg->num_cmds, 0); + if (slot == MAX_TCS_SLOTS) + return -ENOMEM; + i += tcs->ncpt; + } while (slot + msg->num_cmds - 1 >= i); + +copy_data: + bitmap_set(tcs->slots, slot, msg->num_cmds); + /* Copy the addresses of the resources over to the slots */ + for (i = 0; i < msg->num_cmds; i++) + tcs->cmd_cache[slot + i] = msg->cmds[i].addr; + + offset = slot / tcs->ncpt; + *tcs_id = offset + tcs->offset; + *cmd_id = slot % tcs->ncpt; + + return 0; +} + +static int tcs_ctrl_write(struct rsc_drv *drv, const struct tcs_request *msg) +{ + struct tcs_group *tcs; + int tcs_id = 0, cmd_id = 0; + unsigned long flags; + int ret; + + tcs = get_tcs_for_msg(drv, msg); + if (IS_ERR(tcs)) + return PTR_ERR(tcs); + + spin_lock_irqsave(&tcs->lock, flags); + /* find the m-th TCS and the n-th position in the TCS to write to */ + ret = find_slots(tcs, msg, &tcs_id, &cmd_id); + if (!ret) + __tcs_buffer_write(drv, tcs_id, cmd_id, msg); + spin_unlock_irqrestore(&tcs->lock, flags); + + return ret; +} + +/** + * rpmh_rsc_write_ctrl_data: Write request to the controller + * + * @drv: the controller + * @msg: the data to be written to the controller + * + * There is no response returned for writing the request to the controller. + */ +int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, const struct tcs_request *msg) +{ + if (!msg || !msg->cmds || !msg->num_cmds || + msg->num_cmds > MAX_RPMH_PAYLOAD) { + pr_err("Payload error\n"); + return -EINVAL; + } + + /* Data sent to this API will not be sent immediately */ + if (msg->state == RPMH_ACTIVE_ONLY_STATE) + return -EINVAL; + + return tcs_ctrl_write(drv, msg); +} +EXPORT_SYMBOL(rpmh_rsc_write_ctrl_data); + static int rpmh_probe_tcs_config(struct platform_device *pdev, struct rsc_drv *drv) { @@ -428,6 +537,19 @@ static int rpmh_probe_tcs_config(struct platform_device *pdev, tcs->mask = ((1 << tcs->num_tcs) - 1) << st; tcs->offset = st; st += tcs->num_tcs; + + /* + * Allocate memory to cache sleep and wake requests to + * avoid reading TCS register memory. + */ + if (tcs->type == ACTIVE_TCS) + continue; + + tcs->cmd_cache = devm_kcalloc(&pdev->dev, + tcs->num_tcs * ncpt, sizeof(u32), + GFP_KERNEL); + if (!tcs->cmd_cache) + return -ENOMEM; } drv->num_tcs = st; From cd5fd649fdc1137cff5ebc0f3fbfb30c35b33597 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:55 -0600 Subject: [PATCH 11/35] drivers: qcom: rpmh-rsc: allow invalidation of sleep/wake TCS Allow sleep and wake commands to be cleared from the respective TCSes, so that they can be re-populated. Signed-off-by: Lina Iyer --- drivers/soc/qcom/rpmh-rsc.c | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index b5894b001ae107..68c25ebbbe09aa 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -104,6 +104,51 @@ static struct tcs_group *get_tcs_of_type(struct rsc_drv *drv, int type) return &drv->tcs[type]; } +static int tcs_invalidate(struct rsc_drv *drv, int type) +{ + int m; + struct tcs_group *tcs; + + tcs = get_tcs_of_type(drv, type); + if (IS_ERR(tcs)) + return PTR_ERR(tcs); + + spin_lock(&tcs->lock); + if (bitmap_empty(tcs->slots, MAX_TCS_SLOTS)) { + spin_unlock(&tcs->lock); + return 0; + } + + for (m = tcs->offset; m < tcs->offset + tcs->num_tcs; m++) { + if (!tcs_is_free(drv, m)) { + spin_unlock(&tcs->lock); + return -EAGAIN; + } + write_tcs_reg_sync(drv, RSC_DRV_CMD_ENABLE, m, 0); + } + bitmap_zero(tcs->slots, MAX_TCS_SLOTS); + spin_unlock(&tcs->lock); + + return 0; +} + +/** + * rpmh_rsc_invalidate - Invalidate sleep and wake TCSes + * + * @drv: the RSC controller + */ +int rpmh_rsc_invalidate(struct rsc_drv *drv) +{ + int ret; + + ret = tcs_invalidate(drv, SLEEP_TCS); + if (!ret) + ret = tcs_invalidate(drv, WAKE_TCS); + + return ret; +} +EXPORT_SYMBOL(rpmh_rsc_invalidate); + static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, const struct tcs_request *msg) { From 8f8cca2049ee7124853b4cad795e564bab290139 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:56 -0600 Subject: [PATCH 12/35] drivers: qcom: rpmh: cache sleep/wake state requests Active state requests are sent immediately to the RSC controller, while sleep and wake state requests are cached in this driver to avoid taxing the RSC controller repeatedly. The cached values will be sent to the controller when the rpmh_flush() is called. Generally, flushing is a system PM activity and may be called from the system PM drivers when the system is entering suspend or deeper sleep modes during cpuidle. Also allow invalidating the cached requests, so they may be re-populated again. Signed-off-by: Lina Iyer Reviewed-by: Evan Green Reviewed-by: Matthias Kaehlcke --- drivers/soc/qcom/rpmh.c | 217 +++++++++++++++++++++++++++++++++++++++- include/soc/qcom/rpmh.h | 11 ++ 2 files changed, 223 insertions(+), 5 deletions(-) diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 74bb82339b0157..23fcbd9487cdf0 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -7,10 +7,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -33,6 +35,21 @@ .dev = dev, \ } +/** + * struct cache_req: the request object for caching + * + * @addr: the address of the resource + * @sleep_val: the sleep vote + * @wake_val: the wake vote + * @list: linked list obj + */ +struct cache_req { + u32 addr; + u32 sleep_val; + u32 wake_val; + struct list_head list; +}; + /** * struct rpmh_request: the message to be sent to rpmh-rsc * @@ -54,9 +71,15 @@ struct rpmh_request { * struct rpmh_ctrlr: our representation of the controller * * @drv: the controller instance + * @cache: the list of cached requests + * @lock: synchronize access to the controller data + * @dirty: was the cache updated since flush */ struct rpmh_ctrlr { struct rsc_drv *drv; + struct list_head cache; + spinlock_t lock; + bool dirty; }; static struct rpmh_ctrlr rpmh_rsc[RPMH_MAX_CTRLR]; @@ -91,6 +114,8 @@ static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct device *dev) break; } rpmh_rsc[i].drv = drv; + spin_lock_init(&rpmh_rsc[i].lock); + INIT_LIST_HEAD(&rpmh_rsc[i].cache); ctrlr = &rpmh_rsc[i]; break; } @@ -118,29 +143,109 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) } EXPORT_SYMBOL(rpmh_tx_done); +static struct cache_req *__find_req(struct rpmh_ctrlr *ctrlr, u32 addr) +{ + struct cache_req *p, *req = NULL; + + list_for_each_entry(p, &ctrlr->cache, list) { + if (p->addr == addr) { + req = p; + break; + } + } + + return req; +} + +static struct cache_req *cache_rpm_request(struct rpmh_ctrlr *ctrlr, + enum rpmh_state state, + struct tcs_cmd *cmd) +{ + struct cache_req *req; + unsigned long flags; + + spin_lock_irqsave(&ctrlr->lock, flags); + req = __find_req(ctrlr, cmd->addr); + if (req) + goto existing; + + req = kzalloc(sizeof(*req), GFP_ATOMIC); + if (!req) { + req = ERR_PTR(-ENOMEM); + goto unlock; + } + + req->addr = cmd->addr; + req->sleep_val = req->wake_val = UINT_MAX; + INIT_LIST_HEAD(&req->list); + list_add_tail(&req->list, &ctrlr->cache); + +existing: + switch (state) { + case RPMH_ACTIVE_ONLY_STATE: + if (req->sleep_val != UINT_MAX) + req->wake_val = cmd->data; + break; + case RPMH_WAKE_ONLY_STATE: + req->wake_val = cmd->data; + break; + case RPMH_SLEEP_STATE: + req->sleep_val = cmd->data; + break; + default: + break; + }; + + ctrlr->dirty = true; +unlock: + spin_unlock_irqrestore(&ctrlr->lock, flags); + + return req; +} + /** - * __rpmh_write: send the RPMH request + * __rpmh_write: Cache and send the RPMH request * * @dev: The device making the request * @state: Active/Sleep request type * @rpm_msg: The data that needs to be sent (cmds). + * + * Cache the RPMH request and send if the state is ACTIVE_ONLY. + * SLEEP/WAKE_ONLY requests are not sent to the controller at + * this time. Use rpmh_flush() to send them to the controller. */ static int __rpmh_write(const struct device *dev, enum rpmh_state state, struct rpmh_request *rpm_msg) { struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + int ret = -EINVAL; + struct cache_req *req; + int i; if (IS_ERR(ctrlr)) return PTR_ERR(ctrlr); rpm_msg->msg.state = state; - if (state != RPMH_ACTIVE_ONLY_STATE) - return -EINVAL; + /* Cache the request in our store and link the payload */ + for (i = 0; i < rpm_msg->msg.num_cmds; i++) { + req = cache_rpm_request(ctrlr, state, &rpm_msg->msg.cmds[i]); + if (IS_ERR(req)) + return PTR_ERR(req); + } + + rpm_msg->msg.state = state; - WARN_ON(irqs_disabled()); + if (state == RPMH_ACTIVE_ONLY_STATE) { + WARN_ON(irqs_disabled()); + ret = rpmh_rsc_send_data(ctrlr->drv, &rpm_msg->msg); + } else { + ret = rpmh_rsc_write_ctrl_data(ctrlr->drv, &rpm_msg->msg); + /* Clean up our call by spoofing tx_done */ + rpmh_tx_done(&rpm_msg->msg, ret); + } - return rpmh_rsc_send_data(ctrlr->drv, &rpm_msg->msg); + return ret; } /** @@ -174,3 +279,105 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, return (ret > 0) ? 0 : -ETIMEDOUT; } EXPORT_SYMBOL(rpmh_write); + +static int is_req_valid(struct cache_req *req) +{ + return (req->sleep_val != UINT_MAX && + req->wake_val != UINT_MAX && + req->sleep_val != req->wake_val); +} + +static int send_single(const struct device *dev, enum rpmh_state state, + u32 addr, u32 data) +{ + DEFINE_RPMH_MSG_ONSTACK(dev, state, NULL, rpm_msg); + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + + if (IS_ERR(ctrlr)) + return PTR_ERR(ctrlr); + + /* Wake sets are always complete and sleep sets are not */ + rpm_msg.msg.wait_for_compl = (state == RPMH_WAKE_ONLY_STATE); + rpm_msg.cmd[0].addr = addr; + rpm_msg.cmd[0].data = data; + rpm_msg.msg.num_cmds = 1; + + return rpmh_rsc_write_ctrl_data(ctrlr->drv, &rpm_msg.msg); +} + +/** + * rpmh_flush: Flushes the buffered active and sleep sets to TCS + * + * @dev: The device making the request + * + * Return: -EBUSY if the controller is busy, probably waiting on a response + * to a RPMH request sent earlier. + * + * This function is generally called from the sleep code from the last CPU + * that is powering down the entire system. Since no other RPMH API would be + * executing at this time, it is safe to run lockless. + */ +int rpmh_flush(const struct device *dev) +{ + struct cache_req *p; + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + int ret; + + if (IS_ERR(ctrlr)) + return PTR_ERR(ctrlr); + + if (!ctrlr->dirty) { + pr_debug("Skipping flush, TCS has latest data.\n"); + return 0; + } + + /* + * Nobody else should be calling this function other than system PM, + * hence we can run without locks. + */ + list_for_each_entry(p, &ctrlr->cache, list) { + if (!is_req_valid(p)) { + pr_debug("%s: skipping RPMH req: a:%#x s:%#x w:%#x", + __func__, p->addr, p->sleep_val, p->wake_val); + continue; + } + ret = send_single(dev, RPMH_SLEEP_STATE, p->addr, p->sleep_val); + if (ret) + return ret; + ret = send_single(dev, RPMH_WAKE_ONLY_STATE, + p->addr, p->wake_val); + if (ret) + return ret; + } + + ctrlr->dirty = false; + + return 0; +} +EXPORT_SYMBOL(rpmh_flush); + +/** + * rpmh_invalidate: Invalidate all sleep and active sets + * sets. + * + * @dev: The device making the request + * + * Invalidate the sleep and active values in the TCS blocks. + */ +int rpmh_invalidate(const struct device *dev) +{ + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + int ret; + + if (IS_ERR(ctrlr)) + return PTR_ERR(ctrlr); + + ctrlr->dirty = true; + + do { + ret = rpmh_rsc_invalidate(ctrlr->drv); + } while (ret == -EAGAIN); + + return ret; +} +EXPORT_SYMBOL(rpmh_invalidate); diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h index c1d0f902bd7144..42e62a0d26d878 100644 --- a/include/soc/qcom/rpmh.h +++ b/include/soc/qcom/rpmh.h @@ -14,12 +14,23 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n); +int rpmh_flush(const struct device *dev); + +int rpmh_invalidate(const struct device *dev); + #else static inline int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n) { return -ENODEV; } + +static inline int rpmh_flush(const struct device *dev) +{ return -ENODEV; } + +static inline int rpmh_invalidate(const struct device *dev) +{ return -ENODEV; } + #endif /* CONFIG_QCOM_RPMH */ #endif /* __SOC_QCOM_RPMH_H__ */ From 71ecfd13e2b0307192cb2dc593b3d7904409340c Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:57 -0600 Subject: [PATCH 13/35] drivers: qcom: rpmh: allow requests to be sent asynchronously Platform drivers that want to send a request but do not want to block until the RPMH request completes have now a new API - rpmh_write_async(). The API allocates memory and send the requests and returns the control back to the platform driver. The tx_done callback from the controller is handled in the context of the controller's thread and frees the allocated memory. This API allows RPMH requests from atomic contexts as well. Signed-off-by: Lina Iyer --- drivers/soc/qcom/rpmh.c | 52 +++++++++++++++++++++++++++++++++++++++++ include/soc/qcom/rpmh.h | 7 ++++++ 2 files changed, 59 insertions(+) diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 23fcbd9487cdf0..1bb62876795ca6 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -33,6 +33,7 @@ .cmd = { { 0 } }, \ .completion = q, \ .dev = dev, \ + .free = NULL, \ } /** @@ -58,6 +59,7 @@ struct cache_req { * @completion: triggered when request is done * @dev: the device making the request * @err: err return from the controller + * @free: the request object to be freed at tx_done */ struct rpmh_request { struct tcs_request msg; @@ -65,6 +67,7 @@ struct rpmh_request { struct completion *completion; const struct device *dev; int err; + struct rpmh_request *free; }; /** @@ -137,6 +140,8 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) dev_err(rpm_msg->dev, "RPMH TX fail in msg addr=%#x, err=%d\n", rpm_msg->msg.cmds[0].addr, r); + kfree(rpm_msg->free); + /* Signal the blocking thread we are done */ if (compl) complete(compl); @@ -248,6 +253,53 @@ static int __rpmh_write(const struct device *dev, enum rpmh_state state, return ret; } +static struct rpmh_request *__get_rpmh_msg_async(enum rpmh_state state, + const struct tcs_cmd *cmd, + u32 n) +{ + struct rpmh_request *req; + + if (!cmd || !n || n > MAX_RPMH_PAYLOAD) + return ERR_PTR(-EINVAL); + + req = kzalloc(sizeof(*req), GFP_ATOMIC); + if (!req) + return ERR_PTR(-ENOMEM); + + memcpy(req->cmd, cmd, n * sizeof(*cmd)); + + req->msg.state = state; + req->msg.cmds = req->cmd; + req->msg.num_cmds = n; + req->free = req; + + return req; +} + +/** + * rpmh_write_async: Write a set of RPMH commands + * + * @dev: The device making the request + * @state: Active/sleep set + * @cmd: The payload data + * @n: The number of elements in payload + * + * Write a set of RPMH commands, the order of commands is maintained + * and will be sent as a single shot. + */ +int rpmh_write_async(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + struct rpmh_request *rpm_msg; + + rpm_msg = __get_rpmh_msg_async(state, cmd, n); + if (IS_ERR(rpm_msg)) + return PTR_ERR(rpm_msg); + + return __rpmh_write(dev, state, rpm_msg); +} +EXPORT_SYMBOL(rpmh_write_async); + /** * rpmh_write: Write a set of RPMH commands and block until response * diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h index 42e62a0d26d878..1161a5c77e758e 100644 --- a/include/soc/qcom/rpmh.h +++ b/include/soc/qcom/rpmh.h @@ -14,6 +14,9 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n); +int rpmh_write_async(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n); + int rpmh_flush(const struct device *dev); int rpmh_invalidate(const struct device *dev); @@ -24,6 +27,10 @@ static inline int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n) { return -ENODEV; } +static inline int rpmh_write_async(const struct device *dev, + enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ return -ENODEV; } static inline int rpmh_flush(const struct device *dev) { return -ENODEV; } From 7515b745e5e11f998af60abee27986f88e563767 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:58 -0600 Subject: [PATCH 14/35] drivers: qcom: rpmh: add support for batch RPMH request Platform drivers need make a lot of resource state requests at the same time, say, at the start or end of an usecase. It can be quite inefficient to send each request separately. Instead they can give the RPMH library a batch of requests to be sent and wait on the whole transaction to be complete. rpmh_write_batch() is a blocking call that can be used to send multiple RPMH command sets. Each RPMH command set is set asynchronously and the API blocks until all the command sets are complete and receive their tx_done callbacks. Signed-off-by: Lina Iyer --- drivers/soc/qcom/rpmh.c | 154 +++++++++++++++++++++++++++++++++++++++- include/soc/qcom/rpmh.h | 8 +++ 2 files changed, 160 insertions(+), 2 deletions(-) diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 1bb62876795ca6..a0e277b4b84681 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -21,6 +21,8 @@ #include "rpmh-internal.h" #define RPMH_TIMEOUT_MS msecs_to_jiffies(10000) +#define RPMH_MAX_REQ_IN_BATCH 10 +#define RPMH_MAX_BATCH_CACHE (2 * RPMH_MAX_REQ_IN_BATCH) #define DEFINE_RPMH_MSG_ONSTACK(dev, s, q, name) \ struct rpmh_request name = { \ @@ -34,6 +36,7 @@ .completion = q, \ .dev = dev, \ .free = NULL, \ + .wait_count = NULL, \ } /** @@ -60,6 +63,7 @@ struct cache_req { * @dev: the device making the request * @err: err return from the controller * @free: the request object to be freed at tx_done + * @wait_count: count of waiters for this completion */ struct rpmh_request { struct tcs_request msg; @@ -68,6 +72,7 @@ struct rpmh_request { const struct device *dev; int err; struct rpmh_request *free; + atomic_t *wait_count; }; /** @@ -77,12 +82,14 @@ struct rpmh_request { * @cache: the list of cached requests * @lock: synchronize access to the controller data * @dirty: was the cache updated since flush + * @batch_cache: Cache sleep and wake requests sent as batch */ struct rpmh_ctrlr { struct rsc_drv *drv; struct list_head cache; spinlock_t lock; bool dirty; + const struct rpmh_request *batch_cache[RPMH_MAX_BATCH_CACHE]; }; static struct rpmh_ctrlr rpmh_rsc[RPMH_MAX_CTRLR]; @@ -133,6 +140,7 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) struct rpmh_request *rpm_msg = container_of(msg, struct rpmh_request, msg); struct completion *compl = rpm_msg->completion; + atomic_t *wc = rpm_msg->wait_count; rpm_msg->err = r; @@ -143,8 +151,13 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) kfree(rpm_msg->free); /* Signal the blocking thread we are done */ - if (compl) - complete(compl); + if (!compl) + return; + + if (wc && !atomic_dec_and_test(wc)) + return; + + complete(compl); } EXPORT_SYMBOL(rpmh_tx_done); @@ -332,6 +345,137 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, } EXPORT_SYMBOL(rpmh_write); +static int cache_batch(struct rpmh_ctrlr *ctrlr, + struct rpmh_request **rpm_msg, int count) +{ + unsigned long flags; + int ret = 0; + int index = 0; + int i; + + spin_lock_irqsave(&ctrlr->lock, flags); + while (index < RPMH_MAX_BATCH_CACHE && ctrlr->batch_cache[index]) + index++; + if (index + count >= RPMH_MAX_BATCH_CACHE) { + ret = -ENOMEM; + goto fail; + } + + for (i = 0; i < count; i++) + ctrlr->batch_cache[index + i] = rpm_msg[i]; +fail: + spin_unlock_irqrestore(&ctrlr->lock, flags); + + return ret; +} + +static int flush_batch(struct rpmh_ctrlr *ctrlr) +{ + const struct rpmh_request *rpm_msg; + unsigned long flags; + int ret = 0; + int i; + + /* Send Sleep/Wake requests to the controller, expect no response */ + spin_lock_irqsave(&ctrlr->lock, flags); + for (i = 0; ctrlr->batch_cache[i]; i++) { + rpm_msg = ctrlr->batch_cache[i]; + ret = rpmh_rsc_write_ctrl_data(ctrlr->drv, &rpm_msg->msg); + if (ret) + break; + } + spin_unlock_irqrestore(&ctrlr->lock, flags); + + return ret; +} + +static void invalidate_batch(struct rpmh_ctrlr *ctrlr) +{ + unsigned long flags; + int index = 0; + + spin_lock_irqsave(&ctrlr->lock, flags); + while (ctrlr->batch_cache[index]) { + kfree(ctrlr->batch_cache[index]->free); + ctrlr->batch_cache[index] = NULL; + index++; + } + spin_unlock_irqrestore(&ctrlr->lock, flags); +} + +/** + * rpmh_write_batch: Write multiple sets of RPMH commands and wait for the + * batch to finish. + * + * @dev: the device making the request + * @state: Active/sleep set + * @cmd: The payload data + * @n: The array of count of elements in each batch, 0 terminated. + * + * Write a request to the RSC controller without caching. If the request + * state is ACTIVE, then the requests are treated as completion request + * and sent to the controller immediately. The function waits until all the + * commands are complete. If the request was to SLEEP or WAKE_ONLY, then the + * request is sent as fire-n-forget and no ack is expected. + * + * May sleep. Do not call from atomic contexts for ACTIVE_ONLY requests. + */ +int rpmh_write_batch(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 *n) +{ + struct rpmh_request *rpm_msg[RPMH_MAX_REQ_IN_BATCH] = { NULL }; + DECLARE_COMPLETION_ONSTACK(compl); + atomic_t wait_count = ATOMIC_INIT(0); + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + int count = 0; + int ret, i; + + if (IS_ERR(ctrlr) || !cmd || !n) + return -EINVAL; + + while (n[count++] > 0) + ; + count--; + if (!count || count > RPMH_MAX_REQ_IN_BATCH) + return -EINVAL; + + for (i = 0; i < count; i++) { + rpm_msg[i] = __get_rpmh_msg_async(state, cmd, n[i]); + if (IS_ERR_OR_NULL(rpm_msg[i])) { + ret = PTR_ERR(rpm_msg[i]); + for (; i >= 0; i--) + kfree(rpm_msg[i]->free); + return ret; + } + cmd += n[i]; + } + + if (state != RPMH_ACTIVE_ONLY_STATE) + return cache_batch(ctrlr, rpm_msg, count); + + atomic_set(&wait_count, count); + + for (i = 0; i < count; i++) { + rpm_msg[i]->completion = &compl; + rpm_msg[i]->wait_count = &wait_count; + ret = rpmh_rsc_send_data(ctrlr->drv, &rpm_msg[i]->msg); + if (ret) { + int j; + + pr_err("Error(%d) sending RPMH message addr=%#x\n", + ret, rpm_msg[i]->msg.cmds[0].addr); + for (j = i; j < count; j++) + rpmh_tx_done(&rpm_msg[j]->msg, ret); + break; + } + } + + ret = wait_for_completion_timeout(&compl, RPMH_TIMEOUT_MS); + return (ret > 0) ? 0 : -ETIMEDOUT; + +} +EXPORT_SYMBOL(rpmh_write_batch); + static int is_req_valid(struct cache_req *req) { return (req->sleep_val != UINT_MAX && @@ -383,6 +527,11 @@ int rpmh_flush(const struct device *dev) return 0; } + /* First flush the cached batch requests */ + ret = flush_batch(ctrlr); + if (ret) + return ret; + /* * Nobody else should be calling this function other than system PM, * hence we can run without locks. @@ -424,6 +573,7 @@ int rpmh_invalidate(const struct device *dev) if (IS_ERR(ctrlr)) return PTR_ERR(ctrlr); + invalidate_batch(ctrlr); ctrlr->dirty = true; do { diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h index 1161a5c77e758e..619e07c75da9fa 100644 --- a/include/soc/qcom/rpmh.h +++ b/include/soc/qcom/rpmh.h @@ -17,6 +17,9 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, int rpmh_write_async(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n); +int rpmh_write_batch(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 *n); + int rpmh_flush(const struct device *dev); int rpmh_invalidate(const struct device *dev); @@ -32,6 +35,11 @@ static inline int rpmh_write_async(const struct device *dev, const struct tcs_cmd *cmd, u32 n) { return -ENODEV; } +static inline int rpmh_write_batch(const struct device *dev, + enum rpmh_state state, + const struct tcs_cmd *cmd, u32 *n) +{ return -ENODEV; } + static inline int rpmh_flush(const struct device *dev) { return -ENODEV; } From 23b331019974559a584d7cb28409e10a28250db5 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 9 May 2018 11:01:59 -0600 Subject: [PATCH 15/35] drivers: qcom: rpmh-rsc: allow active requests from wake TCS Some RSCs may only have sleep and wake TCS, i.e, there is no dedicated TCS for active mode request, but drivers may still want to make active requests from these RSCs. In such cases re-purpose the wake TCS to send active state requests. The requirement for this is that the driver is aware that the wake TCS is being repurposed to send active request, hence the sleep and wake TCSes be invalidated before the active request is sent. Signed-off-by: Lina Iyer Reviewed-by: Matthias Kaehlcke --- drivers/soc/qcom/rpmh-rsc.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 68c25ebbbe09aa..369b9b3eedc543 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -153,6 +153,7 @@ static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, const struct tcs_request *msg) { int type; + struct tcs_group *tcs; switch (msg->state) { case RPMH_ACTIVE_ONLY_STATE: @@ -168,7 +169,22 @@ static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, return ERR_PTR(-EINVAL); } - return get_tcs_of_type(drv, type); + /* + * If we are making an active request on a RSC that does not have a + * dedicated TCS for active state use, then re-purpose a wake TCS to + * send active votes. + * NOTE: The driver must be aware that this RSC does not have a + * dedicated AMC, and therefore would invalidate the sleep and wake + * TCSes before making an active state request. + */ + tcs = get_tcs_of_type(drv, type); + if (msg->state == RPMH_ACTIVE_ONLY_STATE && IS_ERR(tcs)) { + tcs = get_tcs_of_type(drv, WAKE_TCS); + if (!IS_ERR(tcs)) + rpmh_rsc_invalidate(drv); + } + + return tcs; } static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv, From cf70ddb2847fcd95ed5ffcd5e0b3446160b05f45 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Wed, 23 May 2018 11:04:30 +0530 Subject: [PATCH 16/35] Add RPMH DT entry --- arch/arm64/boot/dts/qcom/sdm845.dtsi | 69 ++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi index d07f2962122ded..32feb8149bbb4b 100644 --- a/arch/arm64/boot/dts/qcom/sdm845.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi @@ -7,6 +7,7 @@ #include #include +#include / { interrupt-parent = <&intc>; @@ -362,5 +363,73 @@ status = "disabled"; }; }; + + apps_rsc: rsc@179c0000 { + label = "apps_rsc"; + compatible = "qcom,rpmh-rsc"; + reg = <0x179c0000 0x10000>, + <0x179d0000 0x10000>, + <0x179e0000 0x10000>; + reg-names = "drv-0", "drv-1", "drv-2"; + interrupts = , + , + ; + qcom,tcs-offset = <0xd00>; + qcom,drv-id = <2>; + qcom,tcs-config = , + , + , + ; + + rpmhpd: power-controller { + compatible = "qcom,sdm845-rpmhpd"; + #power-domain-cells = <1>; + operating-points-v2 = <&rpmhpd_opp_table>, + <&rpmhpd_opp_table>, + <&rpmhpd_opp_table>, + <&rpmhpd_opp_table>, + <&rpmhpd_opp_table>, + <&rpmhpd_opp_table>, + <&rpmhpd_opp_table>, + <&rpmhpd_opp_table>, + <&rpmhpd_opp_table>; + }; + + rpmhpd_opp_table: opp-table { + compatible = "operating-points-v2-qcom-level", "operating-points-v2"; + + rpmhpd_opp1: opp@1 { + qcom-corner = <16>; + }; + + rpmhpd_opp2: opp@2 { + qcom-corner = <48>; + }; + + rpmhpd_opp3: opp@3 { + qcom-corner = <64>; + }; + + rpmhpd_opp4: opp@4 { + qcom-corner = <128>; + }; + + rpmhpd_opp5: opp@5 { + qcom-corner = <192>; + }; + + rpmhpd_opp6: opp@6 { + qcom-corner = <256>; + }; + + rpmhpd_opp7: opp@7 { + qcom-corner = <320>; + }; + + rpmhpd_opp8: opp@8 { + qcom-corner = <416>; + }; + }; + }; }; }; From e70e5b25b01a1f505412f0051ad1351f54990f13 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 16 Mar 2018 09:38:23 +0530 Subject: [PATCH 17/35] mmc: sdhci-msm: Adapt the driver to use OPPs to set clocks/performance state SDHCI driver needs to set a performance state along with scaling its clocks. Modify the driver to use the newly introduced powerdomain performance state based OPPs to scale clocks as well as set an appropriate powerdomain performance state. The patch also adds OPPs for sdhci device on msm8996. On platforms which don't specify an OPP table the driver falls back to just setting the clock rates (as is done today) Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar Cc: linux-mmc@vger.kernel.org --- arch/arm64/boot/dts/qcom/msm8996.dtsi | 41 +++++++++++++++++++ drivers/clk/qcom/gcc-msm8996.c | 8 ++-- drivers/mmc/host/sdhci-msm.c | 59 ++++++++++++++++++++++----- 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/msm8996.dtsi b/arch/arm64/boot/dts/qcom/msm8996.dtsi index 8c7f9ca25b5340..6e684191760947 100644 --- a/arch/arm64/boot/dts/qcom/msm8996.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi @@ -545,8 +545,49 @@ <&gcc GCC_SDCC2_APPS_CLK>, <&xo_board>; bus-width = <4>; + power-domains = <&rpmpd 0>; + operating-points-v2 = <&sdhc_opp_table>; }; + sdhc_opp_table: opp_table { + compatible = "operating-points-v2"; + + opp@144000 { + opp-hz = /bits/ 64 <144000>; + required-opps = <&rpmpd_opp1>; + }; + + opp@4000000 { + opp-hz = /bits/ 64 <400000>; + required-opps = <&rpmpd_opp1>; + }; + + opp@20000000 { + opp-hz = /bits/ 64 <20000000>; + required-opps = <&rpmpd_opp2>; + }; + + opp@25000000 { + opp-hz = /bits/ 64 <25000000>; + required-opps = <&rpmpd_opp2>; + }; + + opp@50000000 { + opp-hz = /bits/ 64 <50000000>; + required-opps = <&rpmpd_opp2>; + }; + + opp@100000000 { + opp-hz = /bits/ 64 <100000000>; + required-opps = <&rpmpd_opp3>; + }; + + opp@200000000 { + opp-hz = /bits/ 64 <200000000>; + required-opps = <&rpmpd_opp3>; + }; + }; + msmgpio: pinctrl@1010000 { compatible = "qcom,msm8996-pinctrl"; reg = <0x01010000 0x300000>; diff --git a/drivers/clk/qcom/gcc-msm8996.c b/drivers/clk/qcom/gcc-msm8996.c index 9f35b3fe1d9731..e530626484e9d0 100644 --- a/drivers/clk/qcom/gcc-msm8996.c +++ b/drivers/clk/qcom/gcc-msm8996.c @@ -464,7 +464,7 @@ static struct clk_rcg2 sdcc1_apps_clk_src = { .name = "sdcc1_apps_clk_src", .parent_names = gcc_xo_gpll0_gpll4_gpll0_early_div, .num_parents = 4, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_ops, }, }; @@ -509,7 +509,7 @@ static struct clk_rcg2 sdcc2_apps_clk_src = { .name = "sdcc2_apps_clk_src", .parent_names = gcc_xo_gpll0_gpll4, .num_parents = 3, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_ops, }, }; @@ -523,7 +523,7 @@ static struct clk_rcg2 sdcc3_apps_clk_src = { .name = "sdcc3_apps_clk_src", .parent_names = gcc_xo_gpll0_gpll4, .num_parents = 3, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_ops, }, }; @@ -547,7 +547,7 @@ static struct clk_rcg2 sdcc4_apps_clk_src = { .name = "sdcc4_apps_clk_src", .parent_names = gcc_xo_gpll0, .num_parents = 2, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_ops, }, }; diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 646bf377ba77bb..ca82b07bc14a62 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "sdhci-pltfm.h" @@ -144,6 +145,7 @@ struct sdhci_msm_host { struct clk *bus_clk; /* SDHC bus voter clock */ struct clk *xo_clk; /* TCXO clk needed for FLL feature of cm_dll*/ struct clk_bulk_data bulk_clks[4]; /* core, iface, cal, sleep clocks */ + struct opp_table *opp_table; unsigned long clk_rate; struct mmc_host *mmc; bool use_14lpp_dll_reset; @@ -158,7 +160,7 @@ struct sdhci_msm_host { u32 caps_0; }; -static unsigned int msm_get_clock_rate_for_bus_mode(struct sdhci_host *host, +static long unsigned int msm_get_clock_rate_for_bus_mode(struct sdhci_host *host, unsigned int clock) { struct mmc_ios ios = host->mmc->ios; @@ -184,18 +186,37 @@ static void msm_set_clock_rate_for_bus_mode(struct sdhci_host *host, struct mmc_ios curr_ios = host->mmc->ios; struct clk *core_clk = msm_host->bulk_clks[0].clk; int rc; + struct device *dev = &msm_host->pdev->dev; + struct dev_pm_opp *opp; + long unsigned int freq; + + if (msm_host->opp_table) { + freq = msm_get_clock_rate_for_bus_mode(host, clock); + opp = dev_pm_opp_find_freq_floor(dev, &freq); + if (IS_ERR(opp)) { + pr_err("%s: failed to find OPP for %u at timing %d\n", + mmc_hostname(host->mmc), clock, curr_ios.timing); + return; + } + rc = dev_pm_opp_set_rate(dev, freq); + if (rc) + pr_err("%s: error in setting opp\n", __func__); - clock = msm_get_clock_rate_for_bus_mode(host, clock); - rc = clk_set_rate(core_clk, clock); - if (rc) { - pr_err("%s: Failed to set clock at rate %u at timing %d\n", - mmc_hostname(host->mmc), clock, - curr_ios.timing); - return; + msm_host->clk_rate = freq; + } else { + clock = msm_get_clock_rate_for_bus_mode(host, clock); + rc = clk_set_rate(core_clk, clock); + if (rc) { + pr_err("%s: Failed to set clock at rate %u at timing %d\n", + mmc_hostname(host->mmc), clock, + curr_ios.timing); + return; + } + msm_host->clk_rate = clock; } - msm_host->clk_rate = clock; + pr_debug("%s: Setting clock at rate %lu at timing %d\n", - mmc_hostname(host->mmc), clk_get_rate(core_clk), + mmc_hostname(host->mmc), msm_host->clk_rate, curr_ios.timing); } @@ -1597,6 +1618,16 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto clk_disable; } + /* Set up the OPP table if it exists */ + msm_host->opp_table = dev_pm_opp_set_clkname(&pdev->dev, "core"); + + ret = dev_pm_opp_of_add_table(&pdev->dev); + if (ret) { + dev_warn(&pdev->dev, "%s: No OPP table specified\n", __func__); + dev_pm_opp_put_clkname(msm_host->opp_table); + msm_host->opp_table = NULL; + } + pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -1619,6 +1650,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); + if (msm_host->opp_table) { + dev_pm_opp_put_clkname(msm_host->opp_table); + dev_pm_opp_of_remove_table(&pdev->dev); + } clk_disable: clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks), msm_host->bulk_clks); @@ -1643,6 +1678,10 @@ static int sdhci_msm_remove(struct platform_device *pdev) pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); + if (msm_host->opp_table) { + dev_pm_opp_put_clkname(msm_host->opp_table); + dev_pm_opp_of_remove_table(&pdev->dev); + } clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks), msm_host->bulk_clks); From ef2345a3bc0d6e10ab9232ed8b75f4829660d715 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 16 Mar 2018 09:38:19 +0530 Subject: [PATCH 18/35] dt-bindings: power: Add qcom rpm power domain driver bindings Add DT bindings to describe the rpm power domains found on Qualcomm Technologies, Inc. SoCs. These power domains communicate a performance state to RPM, which then translates it into corresponding voltage on a PMIC rail. Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar --- .../devicetree/bindings/power/qcom,rpmpd.txt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/qcom,rpmpd.txt diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.txt b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt new file mode 100644 index 00000000000000..61a0e26879404c --- /dev/null +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt @@ -0,0 +1,49 @@ +Qualcomm RPM Power domains + +For RPM Power domains, we communicate a performance state to RPM +which then translates it into a corresponding voltage on a rail + +Required Properties: + - compatible: Should be one of the following + * qcom,msm8996-rpmpd: RPM Power domain for the msm8996 family of SoC + - power-domain-cells: number of cells in Power domain specifier + must be 1. + - operating-points-v2: Phandle to the OPP table for the Power domain. + Refer to Documentation/devicetree/bindings/power/power_domain.txt + and Documentation/devicetree/bindings/opp/qcom-opp.txt for more details + +Example: + + rpmpd: power-controller { + compatible = "qcom,msm8996-rpmpd"; + #power-domain-cells = <1>; + operating-points-v2 = <&rpmpd_opp_table>; + }; + + rpmpd_opp_table: opp-table { + compatible = "operating-points-v2-qcom-level"; + + rpmpd_opp1: opp1 { + qcom,level = <1>; + }; + + rpmpd_opp2: opp2 { + qcom,level = <2>; + }; + + rpmpd_opp3: opp3 { + qcom,level = <3>; + }; + + rpmpd_opp4: opp4 { + qcom,level = <4>; + }; + + rpmpd_opp5: opp5 { + qcom,level = <5>; + }; + + rpmpd_opp6: opp6 { + qcom,level = <6>; + }; + }; From c0d05d008bc7fdf27fcc3f25e19047bc0d6df4ff Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 16 Mar 2018 09:38:19 +0530 Subject: [PATCH 19/35] soc: qcom: rpmpd: Add a Power domain driver to model corners The Power domains for corners just pass the performance state set by the consumers to the RPM (Remote Power manager) which then takes care of setting the appropriate voltage on the corresponding rails to meet the performance needs. We add all Power domain data needed on msm8996 here. This driver can easily be extended by adding data for other qualcomm SoCs as well. Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmpd.c | 301 +++++++++++++++++++++++++ include/dt-bindings/power/qcom-rpmpd.h | 16 ++ 4 files changed, 327 insertions(+) create mode 100644 drivers/soc/qcom/rpmpd.c create mode 100644 include/dt-bindings/power/qcom-rpmpd.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 9dc02f390ba314..5c54931a7b9992 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -74,6 +74,15 @@ config QCOM_RMTFS_MEM Say y here if you intend to boot the modem remoteproc. +config QCOM_RPMPD + tristate "Qualcomm RPM Power domain driver" + depends on MFD_QCOM_RPM && QCOM_SMD_RPM + help + QCOM RPM Power domain driver to support power-domains with + performance states. The driver communicates a performance state + value to RPM which then translates it into corresponding voltage + for the voltage rail. + config QCOM_SMEM tristate "Qualcomm Shared Memory Manager (SMEM)" depends on ARCH_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 19dcf957cb3a05..9550c170de9376 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o +obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c new file mode 100644 index 00000000000000..c5e7a91f278c4d --- /dev/null +++ b/drivers/soc/qcom/rpmpd.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define domain_to_rpmpd(domain) container_of(domain, struct rpmpd, pd) + +/* Resource types */ +#define RPMPD_SMPA 0x61706d73 +#define RPMPD_LDOA 0x616f646c + +/* Operation Keys */ +#define KEY_CORNER 0x6e726f63 /* corn */ +#define KEY_ENABLE 0x6e657773 /* swen */ +#define KEY_FLOOR_CORNER 0x636676 /* vfc */ + +#define DEFINE_RPMPD_CORN_SMPA(_platform, _name, _active, r_id) \ + static struct rpmpd _platform##_##_active; \ + static struct rpmpd _platform##_##_name = { \ + .pd = { .name = #_name, }, \ + .peer = &_platform##_##_active, \ + .res_type = RPMPD_SMPA, \ + .res_id = r_id, \ + .key = KEY_CORNER, \ + }; \ + static struct rpmpd _platform##_##_active = { \ + .pd = { .name = #_active, }, \ + .peer = &_platform##_##_name, \ + .active_only = true, \ + .res_type = RPMPD_SMPA, \ + .res_id = r_id, \ + .key = KEY_CORNER, \ + } + +#define DEFINE_RPMPD_CORN_LDOA(_platform, _name, r_id) \ + static struct rpmpd _platform##_##_name = { \ + .pd = { .name = #_name, }, \ + .res_type = RPMPD_LDOA, \ + .res_id = r_id, \ + .key = KEY_CORNER, \ + } + +#define DEFINE_RPMPD_VFC(_platform, _name, r_id, r_type) \ + static struct rpmpd _platform##_##_name = { \ + .pd = { .name = #_name, }, \ + .res_type = r_type, \ + .res_id = r_id, \ + .key = KEY_FLOOR_CORNER, \ + } + +#define DEFINE_RPMPD_VFC_SMPA(_platform, _name, r_id) \ + DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_SMPA) + +#define DEFINE_RPMPD_VFC_LDOA(_platform, _name, r_id) \ + DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_LDOA) + +struct rpmpd_req { + __le32 key; + __le32 nbytes; + __le32 value; +}; + +struct rpmpd { + struct generic_pm_domain pd; + struct rpmpd *peer; + const bool active_only; + unsigned int corner; + bool enabled; + const char *res_name; + const int res_type; + const int res_id; + struct qcom_smd_rpm *rpm; + __le32 key; +}; + +struct rpmpd_desc { + struct rpmpd **rpmpds; + size_t num_pds; +}; + +static DEFINE_MUTEX(rpmpd_lock); + +/* msm8996 RPM Power domains */ +DEFINE_RPMPD_CORN_SMPA(msm8996, vddcx, vddcx_ao, 1); +DEFINE_RPMPD_CORN_SMPA(msm8996, vddmx, vddmx_ao, 2); +DEFINE_RPMPD_CORN_LDOA(msm8996, vddsscx, 26); + +DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1); +DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26); + +static struct rpmpd *msm8996_rpmpds[] = { + [MSM8996_VDDCX] = &msm8996_vddcx, + [MSM8996_VDDCX_AO] = &msm8996_vddcx_ao, + [MSM8996_VDDCX_VFC] = &msm8996_vddcx_vfc, + [MSM8996_VDDMX] = &msm8996_vddmx, + [MSM8996_VDDMX_AO] = &msm8996_vddmx_ao, + [MSM8996_VDDSSCX] = &msm8996_vddsscx, + [MSM8996_VDDSSCX_VFC] = &msm8996_vddsscx_vfc, +}; + +static const struct rpmpd_desc msm8996_desc = { + .rpmpds = msm8996_rpmpds, + .num_pds = ARRAY_SIZE(msm8996_rpmpds), +}; + +static const struct of_device_id rpmpd_match_table[] = { + { .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc }, + { } +}; +MODULE_DEVICE_TABLE(of, rpmpd_match_table); + +static int rpmpd_send_enable(struct rpmpd *pd, bool enable) +{ + struct rpmpd_req req = { + .key = KEY_ENABLE, + .nbytes = cpu_to_le32(sizeof(u32)), + .value = cpu_to_le32(enable), + }; + + return qcom_rpm_smd_write(pd->rpm, QCOM_RPM_ACTIVE_STATE, pd->res_type, + pd->res_id, &req, sizeof(req)); +} + +static int rpmpd_send_corner(struct rpmpd *pd, int state, unsigned int corner) +{ + struct rpmpd_req req = { + .key = pd->key, + .nbytes = cpu_to_le32(sizeof(u32)), + .value = cpu_to_le32(corner), + }; + + return qcom_rpm_smd_write(pd->rpm, state, pd->res_type, pd->res_id, + &req, sizeof(req)); +}; + +static void to_active_sleep(struct rpmpd *pd, unsigned int corner, + unsigned int *active, unsigned int *sleep) +{ + *active = corner; + + if (pd->active_only) + *sleep = 0; + else + *sleep = *active; +} + +static int rpmpd_aggregate_corner(struct rpmpd *pd) +{ + int ret; + struct rpmpd *peer = pd->peer; + unsigned int active_corner, sleep_corner; + unsigned int this_active_corner = 0, this_sleep_corner = 0; + unsigned int peer_active_corner = 0, peer_sleep_corner = 0; + + to_active_sleep(pd, pd->corner, &this_active_corner, &this_sleep_corner); + + if (peer && peer->enabled) + to_active_sleep(peer, peer->corner, &peer_active_corner, + &peer_sleep_corner); + + active_corner = max(this_active_corner, peer_active_corner); + + ret = rpmpd_send_corner(pd, QCOM_RPM_ACTIVE_STATE, active_corner); + if (ret) + return ret; + + sleep_corner = max(this_sleep_corner, peer_sleep_corner); + + return rpmpd_send_corner(pd, QCOM_RPM_SLEEP_STATE, sleep_corner); +} + +static int rpmpd_power_on(struct generic_pm_domain *domain) +{ + int ret; + struct rpmpd *pd = domain_to_rpmpd(domain); + + mutex_lock(&rpmpd_lock); + + ret = rpmpd_send_enable(pd, true); + if (ret) + goto out; + + pd->enabled = true; + + if (pd->corner) + ret = rpmpd_aggregate_corner(pd); + +out: + mutex_unlock(&rpmpd_lock); + + return ret; +} + +static int rpmpd_power_off(struct generic_pm_domain *domain) +{ + int ret; + struct rpmpd *pd = domain_to_rpmpd(domain); + + mutex_lock(&rpmpd_lock); + + ret = rpmpd_send_enable(pd, false); + if (!ret) + pd->enabled = false; + + mutex_unlock(&rpmpd_lock); + + return ret; +} + +static int rpmpd_probe(struct platform_device *pdev) +{ + int i; + size_t num; + struct genpd_onecell_data *data; + struct qcom_smd_rpm *rpm; + struct rpmpd **rpmpds; + const struct rpmpd_desc *desc; + + rpm = dev_get_drvdata(pdev->dev.parent); + if (!rpm) { + dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n"); + return -ENODEV; + } + + desc = of_device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + rpmpds = desc->rpmpds; + num = desc->num_pds; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->domains = devm_kcalloc(&pdev->dev, num, sizeof(*data->domains), + GFP_KERNEL); + data->num_domains = num; + + for (i = 0; i < num; i++) { + if (!rpmpds[i]) { + dev_warn(&pdev->dev, "rpmpds[] with empty entry at index=%d\n", + i); + continue; + } + + rpmpds[i]->rpm = rpm; + rpmpds[i]->pd.power_off = rpmpd_power_off; + rpmpds[i]->pd.power_on = rpmpd_power_on; + pm_genpd_init(&rpmpds[i]->pd, NULL, true); + + data->domains[i] = &rpmpds[i]->pd; + } + + return of_genpd_add_provider_onecell(pdev->dev.of_node, data); +} + +static int rpmpd_remove(struct platform_device *pdev) +{ + of_genpd_del_provider(pdev->dev.of_node); + return 0; +} + +static struct platform_driver rpmpd_driver = { + .driver = { + .name = "qcom-rpmpd", + .of_match_table = rpmpd_match_table, + }, + .probe = rpmpd_probe, + .remove = rpmpd_remove, +}; + +static int __init rpmpd_init(void) +{ + return platform_driver_register(&rpmpd_driver); +} +core_initcall(rpmpd_init); + +static void __exit rpmpd_exit(void) +{ + platform_driver_unregister(&rpmpd_driver); +} +module_exit(rpmpd_exit); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. RPM Power Domain Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qcom-rpmpd"); diff --git a/include/dt-bindings/power/qcom-rpmpd.h b/include/dt-bindings/power/qcom-rpmpd.h new file mode 100644 index 00000000000000..59f47317b81f65 --- /dev/null +++ b/include/dt-bindings/power/qcom-rpmpd.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */ + +#ifndef _DT_BINDINGS_POWER_QCOM_RPMPD_H +#define _DT_BINDINGS_POWER_QCOM_RPMPD_H + +/* MSM8996 Power Domain Indexes */ +#define MSM8996_VDDCX 0 +#define MSM8996_VDDCX_AO 1 +#define MSM8996_VDDCX_VFC 2 +#define MSM8996_VDDMX 3 +#define MSM8996_VDDMX_AO 4 +#define MSM8996_VDDSSCX 5 +#define MSM8996_VDDSSCX_VFC 6 + +#endif From 7c4c26e7f405484d453c20b17c0ec466d58ca1f2 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 16 Mar 2018 09:38:21 +0530 Subject: [PATCH 20/35] soc: qcom: rpmpd: Add support for get/set performance state Add support for the .set_performace_state() and .opp_to_performance_state() callbacks in the rpmpd driver. Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar --- drivers/soc/qcom/rpmpd.c | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index c5e7a91f278c4d..53209454c1f3db 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,8 @@ #define KEY_ENABLE 0x6e657773 /* swen */ #define KEY_FLOOR_CORNER 0x636676 /* vfc */ +#define MAX_RPMPD_STATE 6 + #define DEFINE_RPMPD_CORN_SMPA(_platform, _name, _active, r_id) \ static struct rpmpd _platform##_##_active; \ static struct rpmpd _platform##_##_name = { \ @@ -221,6 +224,47 @@ static int rpmpd_power_off(struct generic_pm_domain *domain) return ret; } +static int rpmpd_set_performance(struct generic_pm_domain *domain, + unsigned int state) +{ + int ret = 0; + struct rpmpd *pd = domain_to_rpmpd(domain); + + mutex_lock(&rpmpd_lock); + + if (state > MAX_RPMPD_STATE) + goto out; + + pd->corner = state; + + if (!pd->enabled && (pd->key != KEY_FLOOR_CORNER)) + goto out; + + ret = rpmpd_aggregate_corner(pd); + +out: + mutex_unlock(&rpmpd_lock); + + return ret; +} + +static unsigned int rpmpd_get_performance(struct generic_pm_domain *genpd, + struct dev_pm_opp *opp) +{ + struct device_node *np; + unsigned int corner = 0; + + np = dev_pm_opp_get_of_node(opp); + if (of_property_read_u32(np, "qcom,level", &corner)) { + pr_err("%s: missing 'qcom,level' property\n", __func__); + return 0; + } + + of_node_put(np); + + return corner; +} + static int rpmpd_probe(struct platform_device *pdev) { int i; @@ -261,6 +305,8 @@ static int rpmpd_probe(struct platform_device *pdev) rpmpds[i]->rpm = rpm; rpmpds[i]->pd.power_off = rpmpd_power_off; rpmpds[i]->pd.power_on = rpmpd_power_on; + rpmpds[i]->pd.set_performance_state = rpmpd_set_performance; + rpmpds[i]->pd.opp_to_performance_state = rpmpd_get_performance; pm_genpd_init(&rpmpds[i]->pd, NULL, true); data->domains[i] = &rpmpds[i]->pd; From 3f2ae5a5e4168b9bc2088c5e476088a4eaad3d9a Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 16 Mar 2018 09:38:22 +0530 Subject: [PATCH 21/35] arm64: dts: msm8996: Add rpmpd device node Add rpmpd device node and its OPP table Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar --- arch/arm64/boot/dts/qcom/msm8996.dtsi | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/msm8996.dtsi b/arch/arm64/boot/dts/qcom/msm8996.dtsi index 8c7f9ca25b5340..404a08630ccdf7 100644 --- a/arch/arm64/boot/dts/qcom/msm8996.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi @@ -308,6 +308,40 @@ #clock-cells = <1>; }; + rpmpd: power-controller { + compatible = "qcom,msm8996-rpmpd"; + #power-domain-cells = <1>; + operating-points-v2 = <&rpmpd_opp_table>; + }; + + rpmpd_opp_table: opp-table { + compatible = "operating-points-v2-qcom-level"; + + rpmpd_opp1: opp1 { + qcom,level = <1>; + }; + + rpmpd_opp2: opp2 { + qcom,level = <2>; + }; + + rpmpd_opp3: opp3 { + qcom,level = <3>; + }; + + rpmpd_opp4: opp4 { + qcom,level = <4>; + }; + + rpmpd_opp5: opp5 { + qcom,level = <5>; + }; + + rpmpd_opp6: opp6 { + qcom,level = <6>; + }; + }; + pm8994-regulators { compatible = "qcom,rpm-pm8994-regulators"; From 748d22138728d5d0ef62f37d843fbf42c763ccb2 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 9 Mar 2018 13:43:47 +0530 Subject: [PATCH 22/35] dt-bindings: power: Add qcom rpmh power domain driver bindings Add DT bindings to describe the rpmh powerdomains found on Qualcomm Technologies, Inc. SoCs. These power domains communicate a performance state to RPMh, which then translates it into corresponding voltage on a PMIC rail. Signed-off-by: Rajendra Nayak --- .../devicetree/bindings/power/qcom,rpmhpd.txt | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/qcom,rpmhpd.txt diff --git a/Documentation/devicetree/bindings/power/qcom,rpmhpd.txt b/Documentation/devicetree/bindings/power/qcom,rpmhpd.txt new file mode 100644 index 00000000000000..41ef7afa6b24ff --- /dev/null +++ b/Documentation/devicetree/bindings/power/qcom,rpmhpd.txt @@ -0,0 +1,65 @@ +Qualcomm RPMh Power domains + +For RPMh Power domains, we communicate a performance state to RPMh +which then translates it into a corresponding voltage on a rail + +Required Properties: + - compatible: Should be one of the following + * qcom,sdm845-rpmhpd: RPMh Power domain for the sdm845 family of SoC + - power-domain-cells: number of cells in power domain specifier + must be 1 + - operating-points-v2: Phandle to the OPP table for the power-domain. + Refer to Documentation/devicetree/bindings/power/power_domain.txt + and Documentation/devicetree/bindings/opp/qcom-opp.txt for more details + +Example: + + rpmhpd: power-controller { + compatible = "qcom,sdm845-rpmhpd"; + #power-domain-cells = <1>; + operating-points-v2 = <&rpmhpd_opp_table>; + }; + + rpmhpd_opp_table: opp-table { + compatible = "operating-points-v2-qcom-level"; + + rpmhpd_opp_ret: opp1 { + qcom-level = <16>; + }; + + rpmhpd_opp_min_svs: opp2 { + qcom-level = <48>; + }; + + rpmhpd_opp_low_svs: opp3 { + qcom-level = <64>; + }; + + rpmhpd_opp_svs: opp4 { + qcom-level = <128>; + }; + + rpmhpd_opp_svs_l1: opp5 { + qcom-level = <192>; + }; + + rpmhpd_opp_nom: opp6 { + qcom-level = <256>; + }; + + rpmhpd_opp_nom_l1: opp7 { + qcom-level = <320>; + }; + + rpmhpd_opp_nom_l2: opp8 { + qcom-level = <336>; + }; + + rpmhpd_opp_turbo: opp9 { + qcom-level = <384>; + }; + + rpmhpd_opp_turbo_l1: opp10 { + qcom-level = <416>; + }; + }; From dba6830c98f0a5c790a49aa07ead6e1a25b15c34 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 9 Mar 2018 13:43:47 +0530 Subject: [PATCH 23/35] soc: qcom: Add RPMh Power domain driver The RPMh Power domain driver aggregates the corner votes from various consumers for the ARC resources and communicates it to RPMh. We also add data for all power domains on sdm845 SoC as part of the patch. The driver can be extended to support other SoCs which support RPMh Signed-off-by: Rajendra Nayak --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmhpd.c | 427 ++++++++++++++++++++++++ include/dt-bindings/power/qcom-rpmhpd.h | 31 ++ 4 files changed, 468 insertions(+) create mode 100644 drivers/soc/qcom/rpmhpd.c create mode 100644 include/dt-bindings/power/qcom-rpmhpd.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 5c54931a7b9992..7cb7eba2b997a5 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -74,6 +74,15 @@ config QCOM_RMTFS_MEM Say y here if you intend to boot the modem remoteproc. +config QCOM_RPMHPD + tristate "Qualcomm RPMh Power domain driver" + depends on QCOM_RPMH && QCOM_COMMAND_DB + help + QCOM RPMh Power domain driver to support power-domains with + performance states. The driver communicates a performance state + value to RPMh which then translates it into corresponding voltage + for the voltage rail. + config QCOM_RPMPD tristate "Qualcomm RPM Power domain driver" depends on MFD_QCOM_RPM && QCOM_SMD_RPM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 9550c170de9376..499513f63bef58 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o +obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o diff --git a/drivers/soc/qcom/rpmhpd.c b/drivers/soc/qcom/rpmhpd.c new file mode 100644 index 00000000000000..7083ec1590ff5b --- /dev/null +++ b/drivers/soc/qcom/rpmhpd.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018, The Linux Foundation. All rights reserved.*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define domain_to_rpmhpd(domain) container_of(domain, struct rpmhpd, pd) + +#define DEFINE_RPMHPD_AO(_platform, _name, _active) \ + static struct rpmhpd _platform##_##_active; \ + static struct rpmhpd _platform##_##_name = { \ + .pd = { .name = #_name, }, \ + .peer = &_platform##_##_active, \ + .res_name = #_name".lvl", \ + .valid_state_mask = (BIT(RPMH_ACTIVE_ONLY_STATE) | \ + BIT(RPMH_WAKE_ONLY_STATE) | \ + BIT(RPMH_SLEEP_STATE)), \ + }; \ + static struct rpmhpd _platform##_##_active = { \ + .pd = { .name = #_active, }, \ + .peer = &_platform##_##_name, \ + .active_only = true, \ + .res_name = #_name".lvl", \ + .valid_state_mask = (BIT(RPMH_ACTIVE_ONLY_STATE) | \ + BIT(RPMH_WAKE_ONLY_STATE) | \ + BIT(RPMH_SLEEP_STATE)), \ + } + +#define DEFINE_RPMHPD(_platform, _name) \ + static struct rpmhpd _platform##_##_name = { \ + .pd = { .name = #_name, }, \ + .res_name = #_name".lvl", \ + .valid_state_mask = BIT(RPMH_ACTIVE_ONLY_STATE), \ + } + +/* + * This is the number of bytes used for each command DB aux data entry of an + * ARC resource. + */ +#define RPMH_ARC_LEVEL_SIZE 2 +#define RPMH_ARC_MAX_LEVELS 16 + +struct rpmhpd { + struct device *dev; + struct generic_pm_domain pd; + struct rpmhpd *peer; + const bool active_only; + unsigned int corner; + unsigned int active_corner; + u32 level[RPMH_ARC_MAX_LEVELS]; + int level_count; + bool enabled; + const char *res_name; + u32 addr; + u8 valid_state_mask; +}; + +struct rpmhpd_desc { + struct rpmhpd **rpmhpds; + size_t num_pds; +}; + +static DEFINE_MUTEX(rpmhpd_lock); + +/* sdm845 RPMh Power domains */ +DEFINE_RPMHPD(sdm845, ebi); +DEFINE_RPMHPD_AO(sdm845, mx, mx_ao); +DEFINE_RPMHPD_AO(sdm845, cx, cx_ao); +DEFINE_RPMHPD(sdm845, lmx); +DEFINE_RPMHPD(sdm845, lcx); +DEFINE_RPMHPD(sdm845, gfx); +DEFINE_RPMHPD(sdm845, mss); + +static struct rpmhpd *sdm845_rpmhpds[] = { + [SDM845_EBI] = &sdm845_ebi, + [SDM845_MX] = &sdm845_mx, + [SDM845_MX_AO] = &sdm845_mx_ao, + [SDM845_CX] = &sdm845_cx, + [SDM845_CX_AO] = &sdm845_cx_ao, + [SDM845_LMX] = &sdm845_lmx, + [SDM845_LCX] = &sdm845_lcx, + [SDM845_GFX] = &sdm845_gfx, + [SDM845_MSS] = &sdm845_mss, +}; + +static const struct rpmhpd_desc sdm845_desc = { + .rpmhpds = sdm845_rpmhpds, + .num_pds = ARRAY_SIZE(sdm845_rpmhpds), +}; + +static const struct of_device_id rpmhpd_match_table[] = { + { .compatible = "qcom,sdm845-rpmhpd", .data = &sdm845_desc }, + { } +}; +MODULE_DEVICE_TABLE(of, rpmhpd_match_table); + +static int rpmhpd_send_corner(struct rpmhpd *pd, int state, + unsigned int corner, bool sync) +{ + struct tcs_cmd cmd = { + .addr = pd->addr, + .data = corner, + }; + + if (sync) + return rpmh_write(pd->dev, state, &cmd, 1); + else + return rpmh_write_async(pd->dev, state, &cmd, 1); +} + +static int rpmhpd_send_corner_sync(struct rpmhpd *pd, int state, + unsigned int corner) +{ + return rpmhpd_send_corner(pd, state, corner, true); +} + +static int rpmhpd_send_corner_async(struct rpmhpd *pd, int state, + unsigned int corner) +{ + return rpmhpd_send_corner(pd, state, corner, false); +}; + +static void to_active_sleep(struct rpmhpd *pd, unsigned int corner, + unsigned int *active, unsigned int *sleep) +{ + *active = corner; + + if (pd->active_only) + *sleep = 0; + else + *sleep = *active; +} + +/* + * This function is used to aggregate the votes across the active only + * resources and its peers. The aggregated votes are send to RPMh as + * ACTIVE_ONLY votes (which take effect immediately), as WAKE_ONLY votes + * (applied by RPMh on system wakeup) and as SLEEP votes (applied by RPMh + * on system sleep). + * We send ACTIVE_ONLY votes for resources without any peers. For others, + * which have an active only peer, all 3 Votes are sent. + */ +static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) +{ + int ret = -EINVAL; + struct rpmhpd *peer = pd->peer; + unsigned int active_corner, sleep_corner; + unsigned int this_active_corner = 0, this_sleep_corner = 0; + unsigned int peer_active_corner = 0, peer_sleep_corner = 0; + + to_active_sleep(pd, corner, &this_active_corner, &this_sleep_corner); + + if (peer && peer->enabled) + to_active_sleep(peer, peer->corner, &peer_active_corner, + &peer_sleep_corner); + + active_corner = max(this_active_corner, peer_active_corner); + + if (pd->valid_state_mask & BIT(RPMH_ACTIVE_ONLY_STATE)) { + /* + * Wait for an ack only when we are increasing the + * perf state of the power domain + */ + if (active_corner > pd->active_corner) + ret = rpmhpd_send_corner_sync(pd, + RPMH_ACTIVE_ONLY_STATE, + active_corner); + else + ret = rpmhpd_send_corner_async(pd, + RPMH_ACTIVE_ONLY_STATE, + active_corner); + if (ret) + return ret; + pd->active_corner = active_corner; + if (peer) + peer->active_corner = active_corner; + } + + if (pd->valid_state_mask & BIT(RPMH_WAKE_ONLY_STATE)) { + ret = rpmhpd_send_corner_async(pd, RPMH_WAKE_ONLY_STATE, + active_corner); + if (ret) + return ret; + } + + sleep_corner = max(this_sleep_corner, peer_sleep_corner); + + if (pd->valid_state_mask & BIT(RPMH_SLEEP_STATE)) + ret = rpmhpd_send_corner_async(pd, RPMH_SLEEP_STATE, + sleep_corner); + + return ret; +} + +static int rpmhpd_power_on(struct generic_pm_domain *domain) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0; + + mutex_lock(&rpmhpd_lock); + + pd->enabled = true; + + if (pd->corner) + ret = rpmhpd_aggregate_corner(pd, pd->corner); + + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static int rpmhpd_power_off(struct generic_pm_domain *domain) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0; + + mutex_lock(&rpmhpd_lock); + + if (pd->level[0] == 0) + ret = rpmhpd_aggregate_corner(pd, 0); + + if (!ret) + pd->enabled = false; + + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static int rpmhpd_set_performance(struct generic_pm_domain *domain, + unsigned int state) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0, i; + + mutex_lock(&rpmhpd_lock); + + for (i = 0; i < pd->level_count; i++) + if (state <= pd->level[i]) + break; + + if (i == pd->level_count) { + ret = -EINVAL; + dev_err(pd->dev, "invalid state=%u for domain %s", + state, pd->pd.name); + goto out; + } + + pd->corner = i; + + if (!pd->enabled) + goto out; + + ret = rpmhpd_aggregate_corner(pd, i); +out: + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static unsigned int rpmhpd_get_performance(struct generic_pm_domain *genpd, + struct dev_pm_opp *opp) +{ + struct device_node *np; + unsigned int corner = 0; + + np = dev_pm_opp_get_of_node(opp); + if (of_property_read_u32(np, "qcom,level", &corner)) { + pr_err("%s: missing 'qcom,level' property\n", __func__); + return 0; + } + + of_node_put(np); + + return corner; +} + +static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) +{ + int i, j, len, ret; + u8 buf[RPMH_ARC_MAX_LEVELS * RPMH_ARC_LEVEL_SIZE]; + + len = cmd_db_read_aux_data_len(rpmhpd->res_name); + if (len <= 0) + return len; + + if (len > RPMH_ARC_MAX_LEVELS * RPMH_ARC_LEVEL_SIZE) + return -EINVAL; + + ret = cmd_db_read_aux_data(rpmhpd->res_name, buf, len); + if (ret < 0) + return ret; + + rpmhpd->level_count = len / RPMH_ARC_LEVEL_SIZE; + + for (i = 0; i < rpmhpd->level_count; i++) { + rpmhpd->level[i] = 0; + for (j = 0; j < RPMH_ARC_LEVEL_SIZE; j++) + rpmhpd->level[i] |= + buf[i * RPMH_ARC_LEVEL_SIZE + j] << (8 * j); + + /* + * The AUX data may be zero padded. These 0 valued entries at + * the end of the map must be ignored. + */ + if (i > 0 && rpmhpd->level[i] == 0) { + rpmhpd->level_count = i; + break; + } + pr_dbg("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i, + rpmhpd->level[i]); + } + + return 0; +} + +static int rpmhpd_probe(struct platform_device *pdev) +{ + int i, ret; + size_t num; + struct genpd_onecell_data *data; + struct rpmhpd **rpmhpds; + const struct rpmhpd_desc *desc; + + desc = of_device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + rpmhpds = desc->rpmhpds; + num = desc->num_pds; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->domains = devm_kcalloc(&pdev->dev, num, sizeof(*data->domains), + GFP_KERNEL); + data->num_domains = num; + + ret = cmd_db_ready(); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Command DB unavailable, ret=%d\n", + ret); + return ret; + } + + for (i = 0; i < num; i++) { + if (!rpmhpds[i]) { + dev_warn(&pdev->dev, "rpmhpds[] with empty entry at index=%d\n", + i); + continue; + } + + rpmhpds[i]->dev = &pdev->dev; + rpmhpds[i]->addr = cmd_db_read_addr(rpmhpds[i]->res_name); + if (!rpmhpds[i]->addr) { + dev_err(&pdev->dev, "Could not find RPMh address for resource %s\n", + rpmhpds[i]->res_name); + return -ENODEV; + } + + ret = cmd_db_read_slave_id(rpmhpds[i]->res_name); + if (ret != CMD_DB_HW_ARC) { + dev_err(&pdev->dev, "RPMh slave ID mismatch\n"); + return -EINVAL; + } + + ret = rpmhpd_update_level_mapping(rpmhpds[i]); + if (ret) + return ret; + + rpmhpds[i]->pd.power_off = rpmhpd_power_off; + rpmhpds[i]->pd.power_on = rpmhpd_power_on; + rpmhpds[i]->pd.set_performance_state = rpmhpd_set_performance; + rpmhpds[i]->pd.opp_to_performance_state = rpmhpd_get_performance; + pm_genpd_init(&rpmhpds[i]->pd, NULL, true); + + data->domains[i] = &rpmhpds[i]->pd; + } + + return of_genpd_add_provider_onecell(pdev->dev.of_node, data); +} + +static int rpmhpd_remove(struct platform_device *pdev) +{ + of_genpd_del_provider(pdev->dev.of_node); + return 0; +} + +static struct platform_driver rpmhpd_driver = { + .driver = { + .name = "qcom-rpmhpd", + .of_match_table = rpmhpd_match_table, + }, + .probe = rpmhpd_probe, + .remove = rpmhpd_remove, +}; + +static int __init rpmhpd_init(void) +{ + return platform_driver_register(&rpmhpd_driver); +} +core_initcall(rpmhpd_init); + +static void __exit rpmhpd_exit(void) +{ + platform_driver_unregister(&rpmhpd_driver); +} +module_exit(rpmhpd_exit); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. RPMh Power Domain Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qcom-rpmhpd"); diff --git a/include/dt-bindings/power/qcom-rpmhpd.h b/include/dt-bindings/power/qcom-rpmhpd.h new file mode 100644 index 00000000000000..b01ae24526034e --- /dev/null +++ b/include/dt-bindings/power/qcom-rpmhpd.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */ + +#ifndef _DT_BINDINGS_POWER_QCOM_RPMHPD_H +#define _DT_BINDINGS_POWER_QCOM_RPMHPD_H + +/* SDM845 Power Domain Indexes */ +#define SDM845_EBI 0 +#define SDM845_MX 1 +#define SDM845_MX_AO 2 +#define SDM845_CX 3 +#define SDM845_CX_AO 4 +#define SDM845_LMX 5 +#define SDM845_LCX 6 +#define SDM845_GFX 7 +#define SDM845_MSS 8 + +/* SDM845 Power Domain performance levels */ +#define RPMH_REGULATOR_LEVEL_OFF 0 +#define RPMH_REGULATOR_LEVEL_RETENTION 16 +#define RPMH_REGULATOR_LEVEL_MIN_SVS 48 +#define RPMH_REGULATOR_LEVEL_LOW_SVS 64 +#define RPMH_REGULATOR_LEVEL_SVS 128 +#define RPMH_REGULATOR_LEVEL_SVS_L1 192 +#define RPMH_REGULATOR_LEVEL_NOM 256 +#define RPMH_REGULATOR_LEVEL_NOM_L1 320 +#define RPMH_REGULATOR_LEVEL_NOM_L2 336 +#define RPMH_REGULATOR_LEVEL_TURBO 384 +#define RPMH_REGULATOR_LEVEL_TURBO_L1 416 + +#endif From 287397e86db731e5245c172e237642ad5fedbd14 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 16 Mar 2018 09:38:24 +0530 Subject: [PATCH 24/35] soc: qcom: rpmpd/rpmhpd: Add a max vote on all corners at init As we move from no clients/consumers in kernel voting on corners, to *some* voting and some not voting, we might end up in a situation where the clients which remove votes can adversly impact others who still don't have a way to vote. To avoid this situation, have a max vote on all corners at init. This should/can be removed once we have all clients moved to be able to vote/unvote for themselves. Signed-off-by: Rajendra Nayak Acked-by: Viresh Kumar --- drivers/soc/qcom/rpmhpd.c | 12 +++++++++++- drivers/soc/qcom/rpmpd.c | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/rpmhpd.c b/drivers/soc/qcom/rpmhpd.c index 7083ec1590ff5b..3c753d33aeee44 100644 --- a/drivers/soc/qcom/rpmhpd.c +++ b/drivers/soc/qcom/rpmhpd.c @@ -329,7 +329,7 @@ static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) static int rpmhpd_probe(struct platform_device *pdev) { - int i, ret; + int i, ret, max_level; size_t num; struct genpd_onecell_data *data; struct rpmhpd **rpmhpds; @@ -390,6 +390,16 @@ static int rpmhpd_probe(struct platform_device *pdev) pm_genpd_init(&rpmhpds[i]->pd, NULL, true); data->domains[i] = &rpmhpds[i]->pd; + + /* + * Until we have all consumers voting on corners + * just vote the max corner on all PDs + * This should ideally be *removed* once we have + * all (most) consumers being able to vote + */ + max_level = rpmhpds[i]->level_count - 1; + rpmhpd_set_performance(&rpmhpds[i]->pd, rpmhpds[i]->level[max_level]); + rpmhpd_power_on(&rpmhpds[i]->pd); } return of_genpd_add_provider_onecell(pdev->dev.of_node, data); diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index 53209454c1f3db..6440163305e38f 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -310,6 +310,15 @@ static int rpmpd_probe(struct platform_device *pdev) pm_genpd_init(&rpmpds[i]->pd, NULL, true); data->domains[i] = &rpmpds[i]->pd; + + /* + * Until we have all consumers voting on corners + * just vote the max corner on all PDs + * This should ideally be *removed* once we have + * all (most) consumers being able to vote + */ + rpmpd_set_performance(&rpmpds[i]->pd, MAX_RPMPD_STATE); + rpmpd_power_on(&rpmpds[i]->pd); } return of_genpd_add_provider_onecell(pdev->dev.of_node, data); From 9231ea7d0b8312ea6679c2b649cd071411726a33 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Wed, 2 May 2018 13:41:29 -0700 Subject: [PATCH 25/35] print some logs --- drivers/soc/qcom/rpmhpd.c | 47 +++++++++++++++++++++++++++++++++------ drivers/soc/qcom/rpmpd.c | 6 +++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/drivers/soc/qcom/rpmhpd.c b/drivers/soc/qcom/rpmhpd.c index 3c753d33aeee44..da30bba6332034 100644 --- a/drivers/soc/qcom/rpmhpd.c +++ b/drivers/soc/qcom/rpmhpd.c @@ -174,14 +174,18 @@ static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) * Wait for an ack only when we are increasing the * perf state of the power domain */ - if (active_corner > pd->active_corner) + if (active_corner > pd->active_corner) { + printk("Sending active sync %d\n", active_corner); ret = rpmhpd_send_corner_sync(pd, RPMH_ACTIVE_ONLY_STATE, active_corner); - else + } else { + printk("Sending active async %d\n", active_corner); ret = rpmhpd_send_corner_async(pd, RPMH_ACTIVE_ONLY_STATE, active_corner); + } + if (ret) return ret; pd->active_corner = active_corner; @@ -190,6 +194,7 @@ static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) } if (pd->valid_state_mask & BIT(RPMH_WAKE_ONLY_STATE)) { + printk("Sending wake %d\n", active_corner); ret = rpmhpd_send_corner_async(pd, RPMH_WAKE_ONLY_STATE, active_corner); if (ret) @@ -198,9 +203,11 @@ static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) sleep_corner = max(this_sleep_corner, peer_sleep_corner); - if (pd->valid_state_mask & BIT(RPMH_SLEEP_STATE)) + if (pd->valid_state_mask & BIT(RPMH_SLEEP_STATE)) { + printk("Sending sleep %d\n", sleep_corner); ret = rpmhpd_send_corner_async(pd, RPMH_SLEEP_STATE, sleep_corner); + } return ret; } @@ -219,6 +226,8 @@ static int rpmhpd_power_on(struct generic_pm_domain *domain) mutex_unlock(&rpmhpd_lock); + pr_err("%s: %s: %d %d %d\n", __func__, domain->name, pd->corner, __LINE__, ret); + return ret; } @@ -237,6 +246,8 @@ static int rpmhpd_power_off(struct generic_pm_domain *domain) mutex_unlock(&rpmhpd_lock); + pr_err("%s: %s: %d %d %d\n", __func__, domain->name, pd->corner, __LINE__, ret); + return ret; } @@ -268,6 +279,8 @@ static int rpmhpd_set_performance(struct generic_pm_domain *domain, out: mutex_unlock(&rpmhpd_lock); + pr_err("%s: %s: %d %d %d\n", __func__, domain->name, state, pd->corner, ret); + return ret; } @@ -320,7 +333,7 @@ static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) rpmhpd->level_count = i; break; } - pr_dbg("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i, + pr_err("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i, rpmhpd->level[i]); } @@ -397,11 +410,31 @@ static int rpmhpd_probe(struct platform_device *pdev) * This should ideally be *removed* once we have * all (most) consumers being able to vote */ - max_level = rpmhpds[i]->level_count - 1; - rpmhpd_set_performance(&rpmhpds[i]->pd, rpmhpds[i]->level[max_level]); - rpmhpd_power_on(&rpmhpds[i]->pd); + //max_level = rpmhpds[i]->level_count - 1; + //rpmhpd_set_performance(&rpmhpds[i]->pd, rpmhpds[i]->level[max_level]); + //rpmhpd_power_on(&rpmhpds[i]->pd); } + /* test code */ + rpmhpd_set_performance(&rpmhpds[2]->pd, rpmhpds[2]->level[2]); + rpmhpd_power_on(&rpmhpds[2]->pd); + rpmhpd_set_performance(&rpmhpds[1]->pd, rpmhpds[1]->level[4]); + rpmhpd_power_on(&rpmhpds[1]->pd); + + rpmhpd_set_performance(&rpmhpds[2]->pd, rpmhpds[2]->level[3]); + + rpmhpd_set_performance(&rpmhpds[0]->pd, rpmhpds[0]->level[4]); + rpmhpd_power_on(&rpmhpds[0]->pd); + + rpmhpd_power_off(&rpmhpds[2]->pd); + rpmhpd_power_off(&rpmhpds[1]->pd); + rpmhpd_power_off(&rpmhpds[0]->pd); + + rpmhpd_set_performance(&rpmhpds[0]->pd, rpmhpds[0]->level[5]); + rpmhpd_power_on(&rpmhpds[2]->pd); + rpmhpd_power_on(&rpmhpds[1]->pd); + rpmhpd_power_on(&rpmhpds[0]->pd); + return of_genpd_add_provider_onecell(pdev->dev.of_node, data); } diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index 6440163305e38f..02bbdd35ccfe36 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -205,6 +205,8 @@ static int rpmpd_power_on(struct generic_pm_domain *domain) out: mutex_unlock(&rpmpd_lock); + pr_err("%s: %d %d\n", __func__, __LINE__, ret); + return ret; } @@ -221,6 +223,8 @@ static int rpmpd_power_off(struct generic_pm_domain *domain) mutex_unlock(&rpmpd_lock); + pr_err("%s: %d\n", __func__, __LINE__); + return ret; } @@ -245,6 +249,8 @@ static int rpmpd_set_performance(struct generic_pm_domain *domain, out: mutex_unlock(&rpmpd_lock); + pr_err("%s: %s: %d: %d %d\n", __func__, domain->name, __LINE__, state, ret); + return ret; } From 76bad6169c9383d8de8e193c7f1774617c34db9e Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 31 May 2018 12:59:55 +0200 Subject: [PATCH 26/35] PM / Domains: dt: Allow power-domain property to be a list of specifiers To be able to describe topologies where devices are partitioned across multiple power domains, let's extend the power-domain property to allow being a list of PM domain specifiers. Cc: Rob Herring Cc: devicetree@vger.kernel.org Suggested-by: Jon Hunter Signed-off-by: Ulf Hansson Reviewed-by: Rob Herring Reviewed-by: Viresh Kumar --- .../bindings/power/power_domain.txt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/power/power_domain.txt b/Documentation/devicetree/bindings/power/power_domain.txt index 4733f76cbe48da..9b387f861aed16 100644 --- a/Documentation/devicetree/bindings/power/power_domain.txt +++ b/Documentation/devicetree/bindings/power/power_domain.txt @@ -111,8 +111,8 @@ Example 3: ==PM domain consumers== Required properties: - - power-domains : A phandle and PM domain specifier as defined by bindings of - the power controller specified by phandle. + - power-domains : A list of PM domain specifiers, as defined by bindings of + the power controller that is the PM domain provider. Example: @@ -122,9 +122,18 @@ Example: power-domains = <&power 0>; }; -The node above defines a typical PM domain consumer device, which is located -inside a PM domain with index 0 of a power controller represented by a node -with the label "power". + leaky-device@12351000 { + compatible = "foo,i-leak-current"; + reg = <0x12351000 0x1000>; + power-domains = <&power 0>, <&power 1> ; + }; + +The first example above defines a typical PM domain consumer device, which is +located inside a PM domain with index 0 of a power controller represented by a +node with the label "power". +In the second example the consumer device are partitioned across two PM domains, +the first with index 0 and the second with index 1, of a power controller that +is represented by a node with the label "power. Optional properties: - required-opps: This contains phandle to an OPP node in another device's OPP From 06ffb34fb1f72b0b46f2121d9f71eded91d91cd8 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 31 May 2018 12:59:56 +0200 Subject: [PATCH 27/35] PM / Domains: Don't attach devices in genpd with multi PM domains The power-domain DT property may now contain a list of PM domain specifiers, which represents that a device are partitioned across multiple PM domains. This leads to a new situation in genpd_dev_pm_attach(), as only one PM domain can be attached per device. To remain things simple for the most common configuration, when a single PM domain is used, let's treat the multiple PM domain case as being specific. In other words, let's change genpd_dev_pm_attach() to check for multiple PM domains and prevent it from attach any PM domain for this case. Instead, leave this to be managed separately, from following changes to genpd. Cc: Rob Herring Cc: devicetree@vger.kernel.org Suggested-by: Jon Hunter Signed-off-by: Ulf Hansson Acked-by: Jon Hunter Tested-by: Jon Hunter Reviewed-by: Viresh Kumar --- drivers/base/power/domain.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 6f403d6fccb2b3..908c44779ae733 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -2229,10 +2229,10 @@ static void genpd_dev_pm_sync(struct device *dev) * attaches the device to retrieved pm_domain ops. * * Returns 1 on successfully attached PM domain, 0 when the device don't need a - * PM domain or a negative error code in case of failures. Note that if a - * power-domain exists for the device, but it cannot be found or turned on, - * then return -EPROBE_DEFER to ensure that the device is not probed and to - * re-try again later. + * PM domain or when multiple power-domains exists for it, else a negative error + * code. Note that if a power-domain exists for the device, but it cannot be + * found or turned on, then return -EPROBE_DEFER to ensure that the device is + * not probed and to re-try again later. */ int genpd_dev_pm_attach(struct device *dev) { @@ -2243,10 +2243,18 @@ int genpd_dev_pm_attach(struct device *dev) if (!dev->of_node) return 0; + /* + * Devices with multiple PM domains must be attached separately, as we + * can only attach one PM domain per device. + */ + if (of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells") != 1) + return 0; + ret = of_parse_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells", 0, &pd_args); if (ret < 0) - return 0; + return ret; mutex_lock(&gpd_list_lock); pd = genpd_get_from_provider(&pd_args); From 74990e5d2778b8c8df10953aaa722c64fcef2742 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 31 May 2018 12:59:57 +0200 Subject: [PATCH 28/35] PM / Domains: Split genpd_dev_pm_attach() To extend genpd to deal with allowing multiple PM domains per device, some of the code in genpd_dev_pm_attach() can be re-used. Let's prepare for this by moving some of the code into a sub-function. Signed-off-by: Ulf Hansson Acked-by: Jon Hunter Tested-by: Jon Hunter Reviewed-by: Viresh Kumar --- drivers/base/power/domain.c | 60 ++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 908c44779ae733..b1fcbf917974c2 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -2221,38 +2221,15 @@ static void genpd_dev_pm_sync(struct device *dev) genpd_queue_power_off_work(pd); } -/** - * genpd_dev_pm_attach - Attach a device to its PM domain using DT. - * @dev: Device to attach. - * - * Parse device's OF node to find a PM domain specifier. If such is found, - * attaches the device to retrieved pm_domain ops. - * - * Returns 1 on successfully attached PM domain, 0 when the device don't need a - * PM domain or when multiple power-domains exists for it, else a negative error - * code. Note that if a power-domain exists for the device, but it cannot be - * found or turned on, then return -EPROBE_DEFER to ensure that the device is - * not probed and to re-try again later. - */ -int genpd_dev_pm_attach(struct device *dev) +static int __genpd_dev_pm_attach(struct device *dev, struct device_node *np, + unsigned int index) { struct of_phandle_args pd_args; struct generic_pm_domain *pd; int ret; - if (!dev->of_node) - return 0; - - /* - * Devices with multiple PM domains must be attached separately, as we - * can only attach one PM domain per device. - */ - if (of_count_phandle_with_args(dev->of_node, "power-domains", - "#power-domain-cells") != 1) - return 0; - - ret = of_parse_phandle_with_args(dev->of_node, "power-domains", - "#power-domain-cells", 0, &pd_args); + ret = of_parse_phandle_with_args(np, "power-domains", + "#power-domain-cells", index, &pd_args); if (ret < 0) return ret; @@ -2290,6 +2267,35 @@ int genpd_dev_pm_attach(struct device *dev) return ret ? -EPROBE_DEFER : 1; } + +/** + * genpd_dev_pm_attach - Attach a device to its PM domain using DT. + * @dev: Device to attach. + * + * Parse device's OF node to find a PM domain specifier. If such is found, + * attaches the device to retrieved pm_domain ops. + * + * Returns 1 on successfully attached PM domain, 0 when the device don't need a + * PM domain or when multiple power-domains exists for it, else a negative error + * code. Note that if a power-domain exists for the device, but it cannot be + * found or turned on, then return -EPROBE_DEFER to ensure that the device is + * not probed and to re-try again later. + */ +int genpd_dev_pm_attach(struct device *dev) +{ + if (!dev->of_node) + return 0; + + /* + * Devices with multiple PM domains must be attached separately, as we + * can only attach one PM domain per device. + */ + if (of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells") != 1) + return 0; + + return __genpd_dev_pm_attach(dev, dev->of_node, 0); +} EXPORT_SYMBOL_GPL(genpd_dev_pm_attach); static const struct of_device_id idle_state_match[] = { From 99b3d700929addd86b6b3f1ec702dbc79ed07e81 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 31 May 2018 12:59:58 +0200 Subject: [PATCH 29/35] PM / Domains: Add support for multi PM domains per device to genpd To support devices being partitioned across multiple PM domains, let's begin with extending genpd to cope with these kind of configurations. Therefore, add a new exported function genpd_dev_pm_attach_by_id(), which is similar to the existing genpd_dev_pm_attach(), but with the difference that it allows its callers to provide an index to the PM domain that it wants to attach. Note that, genpd_dev_pm_attach_by_id() shall only be called by the driver core / PM core, similar to how the existing dev_pm_domain_attach() makes use of genpd_dev_pm_attach(). However, this is implemented by following changes on top. Because, only one PM domain can be attached per device, genpd needs to create a virtual device that it can attach/detach instead. More precisely, let the new function genpd_dev_pm_attach_by_id() register a virtual struct device via calling device_register(). Then let it attach this device to the corresponding PM domain, rather than the one that is provided by the caller. The actual attaching is done via re-using the existing genpd OF functions. At successful attachment, genpd_dev_pm_attach_by_id() returns the created virtual device, which allows the caller to operate on it to deal with power management. Following changes on top, provides more details in this regards. To deal with detaching of a PM domain for the multiple PM domains case, let's also extend the existing genpd_dev_pm_detach() function, to cover the cleanup of the created virtual device, via make it call device_unregister() on it. In this way, there is no need to introduce a new function to deal with detach for the multiple PM domain case, but instead the existing one is re-used. Signed-off-by: Ulf Hansson Acked-by: Jon Hunter Tested-by: Jon Hunter Reviewed-by: Viresh Kumar --- drivers/base/power/domain.c | 80 +++++++++++++++++++++++++++++++++++++ include/linux/pm_domain.h | 8 ++++ 2 files changed, 88 insertions(+) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index b1fcbf917974c2..4925af5c4cf039 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -2171,6 +2171,15 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np) } EXPORT_SYMBOL_GPL(of_genpd_remove_last); +static void genpd_release_dev(struct device *dev) +{ + kfree(dev); +} + +static struct bus_type genpd_bus_type = { + .name = "genpd", +}; + /** * genpd_dev_pm_detach - Detach a device from its PM domain. * @dev: Device to detach. @@ -2208,6 +2217,10 @@ static void genpd_dev_pm_detach(struct device *dev, bool power_off) /* Check if PM domain can be powered off after removing this device. */ genpd_queue_power_off_work(pd); + + /* Unregister the device if it was created by genpd. */ + if (dev->bus == &genpd_bus_type) + device_unregister(dev); } static void genpd_dev_pm_sync(struct device *dev) @@ -2298,6 +2311,67 @@ int genpd_dev_pm_attach(struct device *dev) } EXPORT_SYMBOL_GPL(genpd_dev_pm_attach); +/** + * genpd_dev_pm_attach_by_id - Associate a device with one of its PM domains. + * @dev: The device used to lookup the PM domain. + * @index: The index of the PM domain. + * + * Parse device's OF node to find a PM domain specifier at the provided @index. + * If such is found, creates a virtual device and attaches it to the retrieved + * pm_domain ops. To deal with detaching of the virtual device, the ->detach() + * callback in the struct dev_pm_domain are assigned to genpd_dev_pm_detach(). + * + * Returns the created virtual device if successfully attached PM domain, NULL + * when the device don't need a PM domain, else an ERR_PTR() in case of + * failures. If a power-domain exists for the device, but cannot be found or + * turned on, then ERR_PTR(-EPROBE_DEFER) is returned to ensure that the device + * is not probed and to re-try again later. + */ +struct device *genpd_dev_pm_attach_by_id(struct device *dev, + unsigned int index) +{ + struct device *genpd_dev; + int num_domains; + int ret; + + if (!dev->of_node) + return NULL; + + /* Deal only with devices using multiple PM domains. */ + num_domains = of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells"); + if (num_domains < 2 || index >= num_domains) + return NULL; + + /* Allocate and register device on the genpd bus. */ + genpd_dev = kzalloc(sizeof(*genpd_dev), GFP_KERNEL); + if (!genpd_dev) + return ERR_PTR(-ENOMEM); + + dev_set_name(genpd_dev, "genpd:%u:%s", index, dev_name(dev)); + genpd_dev->bus = &genpd_bus_type; + genpd_dev->release = genpd_release_dev; + + ret = device_register(genpd_dev); + if (ret) { + kfree(genpd_dev); + return ERR_PTR(ret); + } + + /* Try to attach the device to the PM domain at the specified index. */ + ret = __genpd_dev_pm_attach(genpd_dev, dev->of_node, index); + if (ret < 1) { + device_unregister(genpd_dev); + return ret ? ERR_PTR(ret) : NULL; + } + + pm_runtime_set_active(genpd_dev); + pm_runtime_enable(genpd_dev); + + return genpd_dev; +} +EXPORT_SYMBOL_GPL(genpd_dev_pm_attach_by_id); + static const struct of_device_id idle_state_match[] = { { .compatible = "domain-idle-state", }, { } @@ -2457,6 +2531,12 @@ unsigned int of_genpd_opp_to_performance_state(struct device *dev, } EXPORT_SYMBOL_GPL(of_genpd_opp_to_performance_state); +static int __init genpd_bus_init(void) +{ + return bus_register(&genpd_bus_type); +} +core_initcall(genpd_bus_init); + #endif /* CONFIG_PM_GENERIC_DOMAINS_OF */ diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 42e0d649e65315..82458e8e2e01e1 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -237,6 +237,8 @@ unsigned int of_genpd_opp_to_performance_state(struct device *dev, struct device_node *opp_node); int genpd_dev_pm_attach(struct device *dev); +struct device *genpd_dev_pm_attach_by_id(struct device *dev, + unsigned int index); #else /* !CONFIG_PM_GENERIC_DOMAINS_OF */ static inline int of_genpd_add_provider_simple(struct device_node *np, struct generic_pm_domain *genpd) @@ -282,6 +284,12 @@ static inline int genpd_dev_pm_attach(struct device *dev) return 0; } +static inline struct device *genpd_dev_pm_attach_by_id(struct device *dev, + unsigned int index) +{ + return NULL; +} + static inline struct generic_pm_domain *of_genpd_remove_last(struct device_node *np) { From 5516930d51c2bf7b552b95bf37ca6e698a1f7d06 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 31 May 2018 12:59:59 +0200 Subject: [PATCH 30/35] PM / Domains: Add dev_pm_domain_attach_by_id() to manage multi PM domains The existing dev_pm_domain_attach() function, allows a single PM domain to be attached per device. To be able to support devices that are partitioned across multiple PM domains, let's introduce a new interface, dev_pm_domain_attach_by_id(). The dev_pm_domain_attach_by_id() returns a new allocated struct device with the corresponding attached PM domain. This enables for example a driver to operate on the new device from a power management point of view. The driver may then also benefit from using the received device, to set up so called device-links towards its original device. Depending on the situation, these links may then be dynamically changed. The new interface is typically called by drivers during their probe phase, in case they manages devices which uses multiple PM domains. If that is the case, the driver also becomes responsible of managing the detaching of the PM domains, which typically should be done at the remove phase. Detaching is done by calling the existing dev_pm_domain_detach() function and for each of the received devices from dev_pm_domain_attach_by_id(). Note, currently its only genpd that supports multiple PM domains per device, but dev_pm_domain_attach_by_id() can easily by extended to cover other PM domain types, if/when needed. Signed-off-by: Ulf Hansson Acked-by: Jon Hunter Tested-by: Jon Hunter Reviewed-by: Viresh Kumar --- drivers/base/power/common.c | 43 ++++++++++++++++++++++++++++++++++--- include/linux/pm_domain.h | 7 ++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index 7ae62b6355b8d9..df41b4780b3b71 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -116,14 +116,51 @@ int dev_pm_domain_attach(struct device *dev, bool power_on) } EXPORT_SYMBOL_GPL(dev_pm_domain_attach); +/** + * dev_pm_domain_attach_by_id - Associate a device with one of its PM domains. + * @dev: The device used to lookup the PM domain. + * @index: The index of the PM domain. + * + * As @dev may only be attached to a single PM domain, the backend PM domain + * provider creates a virtual device to attach instead. If attachment succeeds, + * the ->detach() callback in the struct dev_pm_domain are assigned by the + * corresponding backend attach function, as to deal with detaching of the + * created virtual device. + * + * This function should typically be invoked by a driver during the probe phase, + * in case its device requires power management through multiple PM domains. The + * driver may benefit from using the received device, to configure device-links + * towards its original device. Depending on the use-case and if needed, the + * links may be dynamically changed by the driver, which allows it to control + * the power to the PM domains independently from each other. + * + * Callers must ensure proper synchronization of this function with power + * management callbacks. + * + * Returns the virtual created device when successfully attached to its PM + * domain, NULL in case @dev don't need a PM domain, else an ERR_PTR(). + * Note that, to detach the returned virtual device, the driver shall call + * dev_pm_domain_detach() on it, typically during the remove phase. + */ +struct device *dev_pm_domain_attach_by_id(struct device *dev, + unsigned int index) +{ + if (dev->pm_domain) + return ERR_PTR(-EEXIST); + + return genpd_dev_pm_attach_by_id(dev, index); +} +EXPORT_SYMBOL_GPL(dev_pm_domain_attach_by_id); + /** * dev_pm_domain_detach - Detach a device from its PM domain. * @dev: Device to detach. * @power_off: Used to indicate whether we should power off the device. * - * This functions will reverse the actions from dev_pm_domain_attach() and thus - * try to detach the @dev from its PM domain. Typically it should be invoked - * from subsystem level code during the remove phase. + * This functions will reverse the actions from dev_pm_domain_attach() and + * dev_pm_domain_attach_by_id(), thus it detaches @dev from its PM domain. + * Typically it should be invoked during the remove phase, either from + * subsystem level code or from drivers. * * Callers must ensure proper synchronization of this function with power * management callbacks. diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 82458e8e2e01e1..9206a4fef9ac15 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -299,6 +299,8 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np) #ifdef CONFIG_PM int dev_pm_domain_attach(struct device *dev, bool power_on); +struct device *dev_pm_domain_attach_by_id(struct device *dev, + unsigned int index); void dev_pm_domain_detach(struct device *dev, bool power_off); void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd); #else @@ -306,6 +308,11 @@ static inline int dev_pm_domain_attach(struct device *dev, bool power_on) { return 0; } +static inline struct device *dev_pm_domain_attach_by_id(struct device *dev, + unsigned int index) +{ + return NULL; +} static inline void dev_pm_domain_detach(struct device *dev, bool power_off) {} static inline void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd) {} From ca67e64ed43106deaa120aa9ed849b2b13f237d6 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 21 Jun 2018 11:16:09 +0530 Subject: [PATCH 31/35] clk: qcom: Add support to request power domain state There could be single power domain or multiple power domains associated with a clock controller. Add genpd_class support which would help vote/unvote for any power domain performance state for a clock frequency to the genpd framework. A clock frequency request from a consumer would look for the corresponding performance corner and thus would aggregate and request the desired performance state to genpd. Change-Id: I0862ce309e1626fa1749970adfad17128bc7cf27 Signed-off-by: Taniya Das --- drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/clk-genpd.c | 183 +++++++++++++++++++++++++++++++++++ drivers/clk/qcom/clk-genpd.h | 50 ++++++++++ 3 files changed, 234 insertions(+) create mode 100644 drivers/clk/qcom/clk-genpd.c create mode 100644 drivers/clk/qcom/clk-genpd.h diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 762c01137c2fa4..c5945386d01a38 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -12,6 +12,7 @@ clk-qcom-y += clk-regmap-divider.o clk-qcom-y += clk-regmap-mux.o clk-qcom-y += clk-regmap-mux-div.o clk-qcom-y += reset.o +clk-qcom-y += clk-genpd.o clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o # Keep alphabetically sorted by config diff --git a/drivers/clk/qcom/clk-genpd.c b/drivers/clk/qcom/clk-genpd.c new file mode 100644 index 00000000000000..9d350a381bda32 --- /dev/null +++ b/drivers/clk/qcom/clk-genpd.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "clk-genpd.h" +#include "clk-regmap.h" + +struct clk_genpd { + struct list_head list; + struct clk_genpd_class *genpd; +}; + +static LIST_HEAD(clk_genpd_list); + +/* Find the corner required for a given clock rate */ +static int find_rate_to_corner(struct clk_regmap *rclk, unsigned long rate) +{ + int corner; + + for (corner = 0; corner < rclk->genpd->num_corners; corner++) + if (rate <= rclk->rate_max[corner]) + break; + + if (corner == rclk->genpd->num_corners) { + pr_debug("Rate %lu for %s is > than highest Fmax\n", rate, + rclk->hw.init->name); + return -EINVAL; + } + + return corner; +} + +static int genpd_update_corner_state(struct clk_genpd_class *pd) +{ + int corner, ret, *state = pd->corner, i; + int cur_corner = pd->cur_corner, max_corner = pd->num_corners - 1; + + /* Aggregate the corner */ + for (corner = max_corner; corner > 0; corner--) { + if (pd->corner_votes[corner]) + break; + } + + if (corner == cur_corner) + return 0; + + pr_debug("Set performance state to genpd(%s) for state %d\n", + pd->pd_name, state[corner]); + + for (i = 0; i < pd->num_genpd; i++) { + ret = dev_pm_genpd_set_performance_state(pd->genpd_dev[i], + state[corner]); + if (ret) + return ret; + } + + pd->cur_corner = corner; + + return 0; +} + +/* call from prepare */ +int genpd_clk_prepare_vote_rate(struct clk_regmap *rclk, + unsigned long rate) +{ + int corner; + + if (!rclk->genpd) + return 0; + + corner = find_rate_to_corner(rclk, rate); + if (corner < 0) + return corner; + + mutex_lock(&rclk->genpd->lock); + + rclk->genpd->corner_votes[corner]++; + + /* update the corner to genpd */ + if (genpd_update_corner_state(rclk->genpd) < 0) + rclk->genpd->corner_votes[corner]--; + + pr_debug("genpd(%s) prepare corner_votes_count %d, corner %d\n", + rclk->genpd->pd_name, rclk->genpd->corner_votes[corner], + corner); + + mutex_unlock(&rclk->genpd->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(genpd_clk_prepare_vote_rate); + +/* call from unprepare */ +void genpd_clk_unprepare_vote_rate(struct clk_regmap *rclk, + unsigned long rate) +{ + int corner; + + if (!rclk->genpd) + return; + + corner = find_rate_to_corner(rclk, rate); + if (corner < 0) + return; + + if (WARN(!rclk->genpd->corner_votes[corner], + "Reference counts are incorrect for %s corner %d\n", + rclk->genpd->pd_name, corner)) + return; + + mutex_lock(&rclk->genpd->lock); + + rclk->genpd->corner_votes[corner]--; + + if (genpd_update_corner_state(rclk->genpd) < 0) + rclk->genpd->corner_votes[corner]++; + + pr_debug("genpd(%s) unprepare corner_votes_count %d, corner %d\n", + rclk->genpd->pd_name, rclk->genpd->corner_votes[corner], + corner); + + mutex_unlock(&rclk->genpd->lock); +} +EXPORT_SYMBOL_GPL(genpd_clk_unprepare_vote_rate); + +int genpd_class_init(struct device *dev, struct clk_genpd_class *genpd) +{ + struct clk_genpd *pd; + int i, num_domains; + + if (!genpd) { + pr_debug("genpd not defined\n"); + return 0; + } + + /* Deal only with devices using multiple PM domains. */ + num_domains = of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells"); + + list_for_each_entry(pd, &clk_genpd_list, list) { + if (pd->genpd == genpd) { + pr_debug("Genpd is already part of List\n"); + return 0; + } + } + + pr_debug("Voting for genpd_class %s\n", genpd->pd_name); + + if (num_domains == 1) { + genpd->genpd_dev[0] = dev; + } else { + for (i = 0; i < genpd->num_genpd; i++) { + int index = genpd->genpd_index[i]; + + genpd->genpd_dev[i] = genpd_dev_pm_attach_by_id(dev, + index); + } + } + + /* + * vote for max corner during boot -- ToDo, as no current way to remove + * the genpd max vote. + */ + pd = kmalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + pd->genpd = genpd; + + list_add_tail(&pd->list, &clk_genpd_list); + + return 0; +} +EXPORT_SYMBOL_GPL(genpd_class_init); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/qcom/clk-genpd.h b/drivers/clk/qcom/clk-genpd.h new file mode 100644 index 00000000000000..b3b432fbabbced --- /dev/null +++ b/drivers/clk/qcom/clk-genpd.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2013, 2018, The Linux Foundation. All rights reserved. */ + +struct clk_regmap; + +/** + * struct clk_genpd_class - Power Domain scaling class + * @pd_name: name of the power domain class + * @genpd_dev: array of power domain devices + * @num_genpd: size of power domain devices array. + * @genpd_index: array of power domain index which would + * be used to attach the power domain using + * genpd_dev_pm_attach_by_id(dev, index). + * @corner: sorted 2D array of legal corner settings, + indexed by the corner. + * @corner_votes: array of votes for each corner + * @num_corner: specifies the size of corner_votes array + * @cur_corner: current set power domain corner + * @lock: lock to protect this struct + */ +struct clk_genpd_class { + const char *pd_name; + struct device **genpd_dev; + int num_genpd; + int *genpd_index; + int *corner; + int *corner_votes; + int num_corners; + unsigned int cur_corner; + /* Protect this struct */ + struct mutex lock; +}; + +#define CLK_GENPD_INIT(_name, _num_corners, _num_genpd, _corner) \ + struct clk_genpd_class _name = { \ + .pd_name = #_name, \ + .genpd_dev = (struct device *([_num_genpd])) {}, \ + .num_genpd = _num_genpd, \ + .genpd_index = (int [_num_genpd]) {}, \ + .corner_votes = (int [_num_corners]) {}, \ + .num_corners = _num_corners, \ + .corner = _corner, \ + .cur_corner = _num_corners, \ + .lock = __MUTEX_INITIALIZER(_name.lock) \ + } + +int genpd_class_init(struct device *dev, struct clk_genpd_class *genpd); +int genpd_clk_prepare_vote_rate(struct clk_regmap *rclk, unsigned long rate); +void genpd_clk_unprepare_vote_rate(struct clk_regmap *rclk, + unsigned long rate); From 095c4dfbda336726037e8dbab4c16be17616bc8a Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 21 Jun 2018 12:07:49 +0530 Subject: [PATCH 32/35] clk: qcom: Initialize the genpd class for each clock The genpd class is being initialized for clocks which has an associated power domains before registering the clocks with the clock framework. Change-Id: I814b474ef1baf7b7473670c5182d454911b04931 Signed-off-by: Taniya Das --- drivers/clk/qcom/clk-regmap.h | 5 +++++ drivers/clk/qcom/common.c | 17 +++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/clk/qcom/clk-regmap.h b/drivers/clk/qcom/clk-regmap.h index 90d95cd11ec632..2532b00defdc7b 100644 --- a/drivers/clk/qcom/clk-regmap.h +++ b/drivers/clk/qcom/clk-regmap.h @@ -17,22 +17,27 @@ #include struct regmap; +struct clk_genpd_class; /** * struct clk_regmap - regmap supporting clock * @hw: handle between common and hardware-specific interfaces * @regmap: regmap to use for regmap helpers and/or by providers + * @genpd: power domain scaling requirement class * @enable_reg: register when using regmap enable/disable ops * @enable_mask: mask when using regmap enable/disable ops * @enable_is_inverted: flag to indicate set enable_mask bits to disable * when using clock_enable_regmap and friends APIs. + * @rate_max: maximum clock rate in Hz supported at each power domain. */ struct clk_regmap { struct clk_hw hw; struct regmap *regmap; + struct clk_genpd_class *genpd; unsigned int enable_reg; unsigned int enable_mask; bool enable_is_inverted; + unsigned long *rate_max; }; #define to_clk_regmap(_hw) container_of(_hw, struct clk_regmap, hw) diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c index 39ce64c2783b76..3649e50996018f 100644 --- a/drivers/clk/qcom/common.c +++ b/drivers/clk/qcom/common.c @@ -1,14 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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. + * Copyright (c) 2013-2014, 2018, The Linux Foundation. All rights reserved. */ #include @@ -20,6 +12,7 @@ #include #include "common.h" +#include "clk-genpd.h" #include "clk-rcg.h" #include "clk-regmap.h" #include "reset.h" @@ -263,6 +256,10 @@ int qcom_cc_really_probe(struct platform_device *pdev, if (!rclks[i]) continue; + ret = genpd_class_init(dev, rclks[i]->genpd); + if (ret) + return ret; + ret = devm_clk_register_regmap(dev, rclks[i]); if (ret) return ret; From 818cdaf62779ee256be925739592ac99598bbc50 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 21 Jun 2018 12:11:58 +0530 Subject: [PATCH 33/35] clk: qcom: Add prepare/unprepare clock ops for PLL/RCG To put across power domain votes associated with a PLL or RCG, add the prepare/unprepare clock ops which would map the corresponding performance state corners for a clock frequency when the clk_prepare/clk_unprepare is being invoked. Also update the set_rate clock ops to send across the performance state to genpd framework when a new frequency is being requested. Change-Id: Ic4999ddcd894755b972b8c1bd22b890803df680c Signed-off-by: Taniya Das --- drivers/clk/qcom/clk-alpha-pll.c | 52 +++++++++++++++++++++------ drivers/clk/qcom/clk-rcg2.c | 61 ++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 17 deletions(-) diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c index 3c49a60072f149..d658a7715b1946 100644 --- a/drivers/clk/qcom/clk-alpha-pll.c +++ b/drivers/clk/qcom/clk-alpha-pll.c @@ -1,14 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2015, 2018, The Linux Foundation. All rights reserved. - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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 @@ -18,6 +10,7 @@ #include #include "clk-alpha-pll.h" +#include "clk-genpd.h" #include "common.h" #define PLL_MODE(p) ((p)->offset + 0x0) @@ -522,7 +515,9 @@ static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); const struct pll_vco *vco; u32 l, alpha_width = pll_alpha_width(pll); + unsigned long old_rate = clk_hw_get_rate(hw); u64 a; + int ret; rate = alpha_pll_round_rate(rate, prate, &l, &a, alpha_width); vco = alpha_pll_find_vco(pll, rate); @@ -531,6 +526,15 @@ static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, return -EINVAL; } + if (clk_hw_is_prepared(hw)) { + /* Enforce power domain requirement for new frequency */ + ret = genpd_clk_prepare_vote_rate(&pll->clkr, rate); + if (ret) { + pr_err("Failed to vote/set new rate %lu\n", rate); + return ret; + } + } + regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l); if (alpha_width > ALPHA_BITWIDTH) @@ -550,7 +554,15 @@ static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll), PLL_ALPHA_EN, PLL_ALPHA_EN); - return clk_alpha_pll_update_latch(pll, is_enabled); + ret = clk_alpha_pll_update_latch(pll, is_enabled); + if (ret) + old_rate = rate; + + if (clk_hw_is_prepared(hw)) + /* Release the power domain requirement for old frequency */ + genpd_clk_unprepare_vote_rate(&pll->clkr, old_rate); + + return ret; } static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, @@ -1017,7 +1029,25 @@ static int alpha_pll_fabia_set_rate(struct clk_hw *hw, unsigned long rate, return __clk_alpha_pll_update_latch(pll); } +static int clk_alpha_pll_prepare(struct clk_hw *hw) +{ + struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); + unsigned long rate = clk_hw_get_rate(hw); + + return genpd_clk_prepare_vote_rate(&pll->clkr, rate); +} + +static void clk_alpha_pll_unprepare(struct clk_hw *hw) +{ + struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); + unsigned long rate = clk_hw_get_rate(hw); + + genpd_clk_unprepare_vote_rate(&pll->clkr, rate); +} + const struct clk_ops clk_alpha_pll_fabia_ops = { + .prepare = clk_alpha_pll_prepare, + .unprepare = clk_alpha_pll_unprepare, .enable = alpha_pll_fabia_enable, .disable = alpha_pll_fabia_disable, .is_enabled = clk_alpha_pll_is_enabled, @@ -1028,6 +1058,8 @@ const struct clk_ops clk_alpha_pll_fabia_ops = { EXPORT_SYMBOL_GPL(clk_alpha_pll_fabia_ops); const struct clk_ops clk_alpha_pll_fixed_fabia_ops = { + .prepare = clk_alpha_pll_prepare, + .unprepare = clk_alpha_pll_unprepare, .enable = alpha_pll_fabia_enable, .disable = alpha_pll_fabia_disable, .is_enabled = clk_alpha_pll_is_enabled, diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index 52208d4165f432..0c16cbf952659c 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -15,6 +15,7 @@ #include +#include "clk-genpd.h" #include "clk-rcg.h" #include "common.h" @@ -247,26 +248,46 @@ static int __clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f) u32 cfg, mask; struct clk_hw *hw = &rcg->clkr.hw; int ret, index = qcom_find_src_index(hw, rcg->parent_map, f->src); + unsigned long old_rate = clk_hw_get_rate(hw); if (index < 0) return index; + /* Enforce power domain requirement for new frequency */ + if (clk_hw_is_prepared(hw)) { + ret = genpd_clk_prepare_vote_rate(&rcg->clkr, f->freq); + if (ret) { + pr_err("Failed to vote & set new rate %lu\n", f->freq); + return ret; + } + } + if (rcg->mnd_width && f->n) { mask = BIT(rcg->mnd_width) - 1; ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + M_REG, mask, f->m); - if (ret) - return ret; + if (ret) { + /* + * Release the power domain requirement for + * new frequency + */ + old_rate = f->freq; + goto out; + } ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + N_REG, mask, ~(f->n - f->m)); - if (ret) - return ret; + if (ret) { + old_rate = f->freq; + goto out; + } ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + D_REG, mask, ~f->n); - if (ret) - return ret; + if (ret) { + old_rate = f->freq; + goto out; + } } mask = BIT(rcg->hid_width) - 1; @@ -276,8 +297,14 @@ static int __clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f) if (rcg->mnd_width && f->n && (f->m != f->n)) cfg |= CFG_MODE_DUAL_EDGE; - return regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, + ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, mask, cfg); +out: + if (clk_hw_is_prepared(hw)) + /* Release the power domain requirement for old/new frequency */ + genpd_clk_unprepare_vote_rate(&rcg->clkr, old_rate); + + return ret; } static int clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f) @@ -338,7 +365,25 @@ static int clk_rcg2_set_floor_rate_and_parent(struct clk_hw *hw, return __clk_rcg2_set_rate(hw, rate, FLOOR); } +static int clk_rcg2_prepare(struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + unsigned long rate = clk_hw_get_rate(hw); + + return genpd_clk_prepare_vote_rate(&rcg->clkr, rate); +} + +static void clk_rcg2_unprepare(struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + unsigned long rate = clk_hw_get_rate(hw); + + genpd_clk_unprepare_vote_rate(&rcg->clkr, rate); +} + const struct clk_ops clk_rcg2_ops = { + .prepare = clk_rcg2_prepare, + .unprepare = clk_rcg2_unprepare, .is_enabled = clk_rcg2_is_enabled, .get_parent = clk_rcg2_get_parent, .set_parent = clk_rcg2_set_parent, @@ -919,6 +964,8 @@ static void clk_rcg2_shared_disable(struct clk_hw *hw) } const struct clk_ops clk_rcg2_shared_ops = { + .prepare = clk_rcg2_prepare, + .unprepare = clk_rcg2_unprepare, .enable = clk_rcg2_shared_enable, .disable = clk_rcg2_shared_disable, .get_parent = clk_rcg2_get_parent, From 4da3088231c2430609e50889b79edb8eeffcb844 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 21 Jun 2018 12:06:01 +0530 Subject: [PATCH 34/35] clk: qcom: sdm845: Test code for Multiple genpds Change-Id: Ie2f5faf42ea3beac21bc6dc99827000ea1fbf0f6 Signed-off-by: Taniya Das --- arch/arm64/boot/dts/qcom/sdm845.dtsi | 7 +- drivers/clk/qcom/gcc-sdm845.c | 110 +++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 17 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi index 32feb8149bbb4b..84e7a9b7d4c324 100644 --- a/arch/arm64/boot/dts/qcom/sdm845.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi @@ -8,6 +8,7 @@ #include #include #include +#include / { interrupt-parent = <&intc>; @@ -166,8 +167,8 @@ xo_board: xo-board { compatible = "fixed-clock"; #clock-cells = <0>; - clock-frequency = <38400000>; - clock-output-names = "xo_board"; + clock-frequency = <19200000>; + clock-output-names = "bi_tcxo"; }; sleep_clk: sleep-clk { @@ -203,6 +204,8 @@ gcc: clock-controller@100000 { compatible = "qcom,gcc-sdm845"; reg = <0x100000 0x1f0000>; + power-domains = <&rpmhpd SDM845_CX>, + <&rpmhpd SDM845_CX_AO>; #clock-cells = <1>; #reset-cells = <1>; #power-domain-cells = <1>; diff --git a/drivers/clk/qcom/gcc-sdm845.c b/drivers/clk/qcom/gcc-sdm845.c index e78e6f5b99fcc1..c0bb6bfa8954cf 100644 --- a/drivers/clk/qcom/gcc-sdm845.c +++ b/drivers/clk/qcom/gcc-sdm845.c @@ -24,6 +24,11 @@ #include "clk-alpha-pll.h" #include "gdsc.h" #include "reset.h" +#include "clk-genpd.h" +#include "vdd-level.h" + +#include +#include #define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n) } @@ -162,12 +167,22 @@ static const char * const gcc_parent_names_10[] = { "core_bi_pll_test_se", }; +static CLK_GENPD_INIT(vdd_cx, VDD_NUM, 1, vdd_corner); +static CLK_GENPD_INIT(vdd_cx_ao, VDD_NUM, 1, vdd_corner); + static struct clk_alpha_pll gpll0 = { .offset = 0x0, .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_FABIA], .clkr = { .enable_reg = 0x52000, .enable_mask = BIT(0), + .genpd = &vdd_cx, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_MIN] = 615000000, + [VDD_LOW] = 1066000000, + [VDD_LOW_L1] = 1600000000, + [VDD_NOMINAL] = 2000000000, + }, .hw.init = &(struct clk_init_data){ .name = "gpll0", .parent_names = (const char *[]){ "bi_tcxo" }, @@ -183,6 +198,13 @@ static struct clk_alpha_pll gpll4 = { .clkr = { .enable_reg = 0x52000, .enable_mask = BIT(4), + .genpd = &vdd_cx, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_MIN] = 615000000, + [VDD_LOW] = 1066000000, + [VDD_LOW_L1] = 1600000000, + [VDD_NOMINAL] = 2000000000, + }, .hw.init = &(struct clk_init_data){ .name = "gpll4", .parent_names = (const char *[]){ "bi_tcxo" }, @@ -226,11 +248,19 @@ static struct clk_rcg2 gcc_cpuss_ahb_clk_src = { .hid_width = 5, .parent_map = gcc_parent_map_0, .freq_tbl = ftbl_gcc_cpuss_ahb_clk_src, - .clkr.hw.init = &(struct clk_init_data){ - .name = "gcc_cpuss_ahb_clk_src", - .parent_names = gcc_parent_names_7, - .num_parents = 4, - .ops = &clk_rcg2_ops, + .clkr = { + .genpd = &vdd_cx_ao, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_MIN] = 19200000, + [VDD_LOW] = 50000000, + [VDD_NOMINAL] = 100000000, + }, + .hw.init = &(struct clk_init_data){ + .name = "gcc_cpuss_ahb_clk_src", + .parent_names = gcc_parent_names_7, + .num_parents = 4, + .ops = &clk_rcg2_ops, + }, }, }; @@ -268,11 +298,20 @@ static struct clk_rcg2 gcc_gp1_clk_src = { .hid_width = 5, .parent_map = gcc_parent_map_1, .freq_tbl = ftbl_gcc_gp1_clk_src, - .clkr.hw.init = &(struct clk_init_data){ - .name = "gcc_gp1_clk_src", - .parent_names = gcc_parent_names_1, - .num_parents = 5, - .ops = &clk_rcg2_ops, + .clkr = { + .genpd = &vdd_cx, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_MIN] = 19200000, + [VDD_LOWER] = 50000000, + [VDD_LOW] = 100000000, + [VDD_NOMINAL] = 200000000, + }, + .hw.init = &(struct clk_init_data){ + .name = "gcc_gp1_clk_src", + .parent_names = gcc_parent_names_1, + .num_parents = 5, + .ops = &clk_rcg2_ops, + }, }, }; @@ -282,11 +321,20 @@ static struct clk_rcg2 gcc_gp2_clk_src = { .hid_width = 5, .parent_map = gcc_parent_map_1, .freq_tbl = ftbl_gcc_gp1_clk_src, - .clkr.hw.init = &(struct clk_init_data){ - .name = "gcc_gp2_clk_src", - .parent_names = gcc_parent_names_1, - .num_parents = 5, - .ops = &clk_rcg2_ops, + .clkr = { + .genpd = &vdd_cx, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_MIN] = 19200000, + [VDD_LOWER] = 50000000, + [VDD_LOW] = 100000000, + [VDD_NOMINAL] = 200000000, + }, + .hw.init = &(struct clk_init_data){ + .name = "gcc_gp2_clk_src", + .parent_names = gcc_parent_names_1, + .num_parents = 5, + .ops = &clk_rcg2_ops, + }, }, }; @@ -3437,6 +3485,10 @@ static int gcc_sdm845_probe(struct platform_device *pdev) regmap_update_bits(regmap, 0x48190, BIT(0), 0x1); regmap_update_bits(regmap, 0x52004, BIT(22), 0x1); + /* Indexes of the power domain */ + vdd_cx.genpd_index[0] = 0; + vdd_cx_ao.genpd_index[0] = 1; + return qcom_cc_really_probe(pdev, &gcc_sdm845_desc, regmap); } @@ -3460,6 +3512,34 @@ static void __exit gcc_sdm845_exit(void) } module_exit(gcc_sdm845_exit); +static int gcc_late_init_genpd(void) +{ + pr_alert("This will test the genpd voting for PLLs\n"); + + pr_err("GPLL4 prepare\n"); + clk_prepare(gpll4.clkr.hw.clk); + mdelay(10); + pr_err("GPLL4 enable\n"); + clk_enable(gpll4.clkr.hw.clk); + mdelay(20); + pr_err("GPLL4 disable and unprepare\n"); + clk_disable_unprepare(gpll4.clkr.hw.clk); + + pr_err("GP1 clksrc set rate 25000000\n"); + clk_set_rate(gcc_gp1_clk_src.clkr.hw.clk, 25000000); + pr_err("GP1 prepare_enable 25000000\n"); + clk_prepare_enable(gcc_gp1_clk.clkr.hw.clk); + mdelay(50); + pr_err("GP1 clksrc set rate 200000000\n"); + clk_set_rate(gcc_gp1_clk.clkr.hw.clk, 200000000); + pr_err("GP2 clksrc set rate 200000000\n"); + clk_set_rate(gcc_gp2_clk_src.clkr.hw.clk, 200000000); + + return 0; +} + +late_initcall(gcc_late_init_genpd) + MODULE_DESCRIPTION("QTI GCC SDM845 Driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:gcc-sdm845"); From 4fa606fb750289bbf1b8b8ffa56542b2f4723491 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 21 Jun 2018 12:06:01 +0530 Subject: [PATCH 35/35] clk: qcom: sdm845: Test code for Multiple genpds Change-Id: Ie2f5faf42ea3beac21bc6dc99827000ea1fbf0f6 Signed-off-by: Taniya Das --- drivers/clk/qcom/vdd-level.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 drivers/clk/qcom/vdd-level.h diff --git a/drivers/clk/qcom/vdd-level.h b/drivers/clk/qcom/vdd-level.h new file mode 100644 index 00000000000000..0cc724cf5659a5 --- /dev/null +++ b/drivers/clk/qcom/vdd-level.h @@ -0,0 +1,28 @@ +#ifndef __DRIVERS_CLK_QCOM_VDD_LEVEL_H +#define __DRIVERS_CLK_QCOM_VDD_LEVEL_H + +#include + +enum vdd_levels { + VDD_NONE, + VDD_MIN, /* MIN SVS */ + VDD_LOWER, /* SVS2 */ + VDD_LOW, /* SVS */ + VDD_LOW_L1, /* SVSL1 */ + VDD_NOMINAL, /* NOM */ + VDD_HIGH, /* TURBO */ + VDD_NUM, +}; + +static int vdd_corner[] = { + RPMH_REGULATOR_LEVEL_OFF, /* VDD_NONE */ + RPMH_REGULATOR_LEVEL_MIN_SVS, /* VDD_MIN */ + RPMH_REGULATOR_LEVEL_LOW_SVS, /* VDD_LOWER */ + RPMH_REGULATOR_LEVEL_SVS, /* VDD_LOW */ + RPMH_REGULATOR_LEVEL_SVS_L1, /* VDD_LOW_L1 */ + RPMH_REGULATOR_LEVEL_NOM, /* VDD_NOMINAL */ + RPMH_REGULATOR_LEVEL_TURBO, /* VDD_HIGH */ +}; + +#endif +