diff options
author | Linaro CI <ci_notify@linaro.org> | 2018-06-02 19:49:30 +0000 |
---|---|---|
committer | Linaro CI <ci_notify@linaro.org> | 2018-06-02 19:49:30 +0000 |
commit | af470c30e2e8b7b27248abace08c5653799a1d19 (patch) | |
tree | f7218ab516d6e9f1feccfc9fdc83fd0740ac061e | |
parent | f8319da8ffd41ee7474d0ba87d0242f04b72f217 (diff) | |
parent | 0603f2c79128d3b55c5768669bb05705466a27f6 (diff) |
Merge remote-tracking branch 'sdm845-rpmh/lkml/rpmh-v5' into integration-linux-qcomlt
# Conflicts:
# drivers/soc/qcom/Makefile
-rw-r--r-- | Documentation/devicetree/bindings/soc/qcom/rpmh-rsc.txt | 127 | ||||
-rw-r--r-- | drivers/soc/qcom/Kconfig | 10 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 4 | ||||
-rw-r--r-- | drivers/soc/qcom/rpmh-internal.h | 98 | ||||
-rw-r--r-- | drivers/soc/qcom/rpmh-rsc.c | 767 | ||||
-rw-r--r-- | drivers/soc/qcom/rpmh.c | 659 | ||||
-rw-r--r-- | drivers/soc/qcom/trace-rpmh.h | 89 | ||||
-rw-r--r-- | include/dt-bindings/soc/qcom,rpmh-rsc.h | 14 | ||||
-rw-r--r-- | include/soc/qcom/rpmh.h | 60 | ||||
-rw-r--r-- | include/soc/qcom/tcs.h | 56 |
10 files changed, 1884 insertions, 0 deletions
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 000000000000..dcf71a5b302f --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/rpmh-rsc.txt @@ -0,0 +1,127 @@ +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 a 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: <string> + Definition: Should be "qcom,rpmh-rsc". + +- reg: + Usage: required + Value type: <prop-encoded-array> + Definition: The first register specifies the base address of the DRV. + The second register specifies the start address of the + TCS. + +- reg-names: + Usage: required + Value type: <string> + Definition: Maps the register specified in the reg property. Must be + "drv" and "tcs". + +- interrupts: + Usage: required + Value type: <prop-encoded-interrupt> + 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: <u32> + Definition: the id of the DRV in the RSC block. + +- qcom,tcs-config: + Usage: required + Value type: <prop-encoded-array> + Definition: the tuple defining the configuration of TCS. + Must have 2 cells which describe each TCS type. + <type number_of_tcs>. + 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): <u32> + +- label: + Usage: optional + Value type: <string> + 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 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 - +First tuple: 0x179C0000 + 0x10000 * 2 = 0x179E0000 +Second tuple: 0x179E0000 + 0xD00 = 0x179E0D00 + + apps_rsc: rsc@179e000 { + label = "apps_rsc"; + compatible = "qcom,rpmh-rsc"; + reg = <0x179e0000 0x10000>, <0x179e0d00 0x3000>; + reg-names = "drv", "tcs"; + interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>; + qcom,drv-id = <2>; + qcom,tcs-config = <SLEEP_TCS 3>, + <WAKE_TCS 3>, + <ACTIVE_TCS 2>, + <CONTROL_TCS 1>; + }; + +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 - +First tuple: 0xAF20000 +Second tuple: 0xAF20000 + 0x1C00 = 0xAF21C00 + + disp_rsc: rsc@af20000 { + label = "disp_rsc"; + compatible = "qcom,rpmh-rsc"; + reg = <0xaf20000 0x10000>, <0xaf21c00 0x3000>; + reg-names = "drv", "tcs"; + interrupts = <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH>; + qcom,drv-id = <0>; + qcom,tcs-config = <SLEEP_TCS 1>, + <WAKE_TCS 1>, + <ACTIVE_TCS 0>, + <CONTROL_TCS 0>; + }; diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 9dc02f390ba3..c193f460eb4f 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 12fd691bc1a5..6cee9d7bb6a6 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o +CFLAGS_rpmh-rsc.o := -I$(src) obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o @@ -8,6 +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) += 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 new file mode 100644 index 000000000000..2fc309f185fe --- /dev/null +++ b/drivers/soc/qcom/rpmh-internal.h @@ -0,0 +1,98 @@ +/* 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 <linux/bitmap.h> +#include <soc/qcom/tcs.h> + +#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) +#define MAX_TCS_SLOTS (MAX_CMDS_PER_TCS * MAX_TCS_PER_TYPE) + +struct rsc_drv; + +/** + * struct tcs_response: Response object for a request + * + * @drv: the controller + * @msg: the request for this response + * @m: the tcs identifier + * @err: error reported in the response + * @list: element in list of pending response objects + */ +struct tcs_response { + struct rsc_drv *drv; + const struct tcs_request *msg; + int err; + struct list_head list; +}; + +/** + * struct tcs_group: group of Trigger Command Sets for a request state + * + * @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 + * @responses: response objects for requests sent from each 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; + int type; + u32 mask; + u32 offset; + int num_tcs; + int ncpt; + spinlock_t lock; + struct tcs_response *responses[MAX_TCS_PER_TYPE]; + u32 *cmd_cache; + DECLARE_BITMAP(slots, MAX_TCS_SLOTS); +}; + +/** + * struct rsc_drv: the Resource State Coordinator controller + * + * @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 + * @tasklet: handle responses, off-load work from IRQ handler + * @response_pending: + * list of responses that needs to be sent to caller + * @tcs: TCS groups + * @tcs_in_use: s/w state of the TCS + * @drv_lock: synchronize state of the controller + */ +struct rsc_drv { + const char *name; + void __iomem *tcs_base; + int id; + int num_tcs; + struct tasklet_struct tasklet; + struct list_head response_pending; + struct tcs_group tcs[TCS_TYPE_NR]; + DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR); + spinlock_t drv_lock; +}; + + +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); + +#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 000000000000..57661b7fea79 --- /dev/null +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -0,0 +1,767 @@ +// 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 <linux/atomic.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include <soc/qcom/tcs.h> +#include <dt-bindings/soc/qcom,rpmh-rsc.h> + +#include "rpmh-internal.h" + +#define CREATE_TRACE_POINTS +#include "trace-rpmh.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 struct tcs_group *get_tcs_from_index(struct rsc_drv *drv, int m) +{ + struct tcs_group *tcs; + int i; + + for (i = 0; i < drv->num_tcs; i++) { + tcs = &drv->tcs[i]; + if (tcs->mask & BIT(m)) + return tcs; + } + + WARN(i == drv->num_tcs, "Incorrect TCS index %d", m); + + return NULL; +} + +static struct tcs_response *setup_response(struct rsc_drv *drv, + const struct tcs_request *msg, int m) +{ + struct tcs_response *resp; + struct tcs_group *tcs; + + resp = kzalloc(sizeof(*resp), GFP_ATOMIC); + if (!resp) + return ERR_PTR(-ENOMEM); + + resp->drv = drv; + resp->msg = msg; + resp->err = 0; + + tcs = get_tcs_from_index(drv, m); + if (!tcs) + return ERR_PTR(-EINVAL); + + assert_spin_locked(&tcs->lock); + tcs->responses[m - tcs->offset] = resp; + + return resp; +} + +static void free_response(struct tcs_response *resp) +{ + kfree(resp); +} + +static struct tcs_response *get_response(struct rsc_drv *drv, u32 m) +{ + struct tcs_group *tcs = get_tcs_from_index(drv, m); + + return tcs->responses[m - tcs->offset]; +} + +static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int m, int n) +{ + return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * m + + RSC_DRV_CMD_OFFSET * n); +} + +static void write_tcs_reg(struct rsc_drv *drv, int reg, int m, int n, u32 data) +{ + writel_relaxed(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * m + + RSC_DRV_CMD_OFFSET * n); +} + +static void write_tcs_reg_sync(struct rsc_drv *drv, int reg, int m, int n, + u32 data) +{ + write_tcs_reg(drv, reg, m, n, data); + for (;;) { + if (data == read_tcs_reg(drv, reg, m, n)) + break; + udelay(1); + } +} + +static bool tcs_is_free(struct rsc_drv *drv, int m) +{ + return !test_bit(m, drv->tcs_in_use) && + read_tcs_reg(drv, RSC_DRV_STATUS, m, 0); +} + +static struct tcs_group *get_tcs_of_type(struct rsc_drv *drv, int type) +{ + int i; + struct tcs_group *tcs; + + for (i = 0; i < TCS_TYPE_NR; i++) { + if (type == drv->tcs[i].type) + break; + } + + if (i == TCS_TYPE_NR) + return ERR_PTR(-EINVAL); + + tcs = &drv->tcs[i]; + if (!tcs->num_tcs) + return ERR_PTR(-EINVAL); + + return tcs; +} + +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, 0); + bitmap_zero(tcs->slots, MAX_TCS_SLOTS); + } + spin_unlock(&tcs->lock); + + return 0; +} + +/** + * rpmh_rsc_invalidate - Invalidate sleep and wake TCSes + * + * @drv: the mailbox controller + */ +int rpmh_rsc_invalidate(struct rsc_drv *drv) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&drv->drv_lock, flags); + ret = __tcs_invalidate(drv, SLEEP_TCS); + if (!ret) + ret = __tcs_invalidate(drv, WAKE_TCS); + spin_unlock_irqrestore(&drv->drv_lock, flags); + + return ret; +} +EXPORT_SYMBOL(rpmh_rsc_invalidate); + +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: + 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); + } + + /* + * 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 void send_tcs_response(struct tcs_response *resp) +{ + struct rsc_drv *drv; + unsigned long flags; + + if (!resp) + return; + + drv = resp->drv; + spin_lock_irqsave(&drv->drv_lock, flags); + INIT_LIST_HEAD(&resp->list); + list_add_tail(&resp->list, &drv->response_pending); + spin_unlock_irqrestore(&drv->drv_lock, flags); + + tasklet_schedule(&drv->tasklet); +} + +/** + * tcs_irq_handler: TX Done interrupt handler + */ +static irqreturn_t tcs_irq_handler(int irq, void *p) +{ + struct rsc_drv *drv = p; + int m, i; + u32 irq_status, sts; + struct tcs_response *resp; + struct tcs_cmd *cmd; + + irq_status = read_tcs_reg(drv, RSC_DRV_IRQ_STATUS, 0, 0); + + for (m = 0; m < drv->num_tcs; m++) { + if (!(irq_status & (u32)BIT(m))) + continue; + + resp = get_response(drv, m); + if (WARN_ON(!resp)) + goto skip_resp; + + resp->err = 0; + for (i = 0; i < resp->msg->num_cmds; i++) { + cmd = &resp->msg->cmds[i]; + sts = read_tcs_reg(drv, RSC_DRV_CMD_STATUS, m, i); + if (!(sts & CMD_STATUS_ISSUED) || + ((resp->msg->wait_for_compl || cmd->wait) && + !(sts & CMD_STATUS_COMPL))) { + resp->err = -EIO; + break; + } + } +skip_resp: + /* Reclaim the TCS */ + write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, m, 0, 0); + write_tcs_reg(drv, RSC_DRV_IRQ_CLEAR, 0, 0, BIT(m)); + trace_rpmh_notify_irq(drv, resp); + clear_bit(m, drv->tcs_in_use); + send_tcs_response(resp); + } + + return IRQ_HANDLED; +} + +/** + * tcs_notify_tx_done: TX Done for requests that got a response + * + * @data: the tasklet argument + * + * Tasklet function to notify MBOX that we are done with the request. + * Handles all pending reponses whenever run. + */ +static void tcs_notify_tx_done(unsigned long data) +{ + struct rsc_drv *drv = (struct rsc_drv *)data; + struct tcs_response *resp; + unsigned long flags; + const struct tcs_request *msg; + int err; + + for (;;) { + spin_lock_irqsave(&drv->drv_lock, flags); + resp = list_first_entry_or_null(&drv->response_pending, + struct tcs_response, list); + if (!resp) { + spin_unlock_irqrestore(&drv->drv_lock, flags); + break; + } + list_del(&resp->list); + spin_unlock_irqrestore(&drv->drv_lock, flags); + trace_rpmh_notify_tx_done(drv, resp); + msg = resp->msg; + err = resp->err; + free_response(resp); + rpmh_tx_done(msg, err); + } +} + +static void __tcs_buffer_write(struct rsc_drv *drv, int m, int n, + 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, m, 0); + + for (i = 0, j = n; 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_reg(drv, RSC_DRV_CMD_MSGID, m, j, msgid); + write_tcs_reg(drv, RSC_DRV_CMD_ADDR, m, j, cmd->addr); + write_tcs_reg(drv, RSC_DRV_CMD_DATA, m, j, cmd->data); + trace_rpmh_send_msg(drv, m, j, msgid, cmd); + } + + write_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, m, 0, cmd_complete); + cmd_enable |= read_tcs_reg(drv, RSC_DRV_CMD_ENABLE, m, 0); + write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, m, 0, cmd_enable); +} + +static void __tcs_trigger(struct rsc_drv *drv, int m) +{ + 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, m, 0); + enable &= ~TCS_AMC_MODE_TRIGGER; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, m, 0, enable); + enable &= ~TCS_AMC_MODE_ENABLE; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, m, 0, 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, m, 0, enable); + enable |= TCS_AMC_MODE_TRIGGER; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, m, 0, 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 m = tcs->offset; + + for (i = 0; i < tcs->num_tcs; i++, m++) { + if (tcs_is_free(drv, m)) + continue; + + curr_enabled = read_tcs_reg(drv, RSC_DRV_CMD_ENABLE, m, 0); + + for_each_set_bit(j, &curr_enabled, MAX_CMDS_PER_TCS) { + addr = read_tcs_reg(drv, RSC_DRV_CMD_ADDR, m, 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 m; + + for (m = 0; m < tcs->num_tcs; m++) { + if (tcs_is_free(tcs->drv, tcs->offset + m)) + return tcs->offset + m; + } + + return -EBUSY; +} + +static int tcs_mbox_write(struct rsc_drv *drv, const struct tcs_request *msg) +{ + struct tcs_group *tcs; + int m; + struct tcs_response *resp = NULL; + 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); + m = find_free_tcs(tcs); + if (m < 0) { + ret = m; + goto done_write; + } + + /* + * 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) + goto done_write; + + resp = setup_response(drv, msg, m); + if (IS_ERR(resp)) { + ret = PTR_ERR(resp); + goto done_write; + } + + set_bit(m, drv->tcs_in_use); + __tcs_buffer_write(drv, m, 0, msg); + __tcs_trigger(drv, m); + +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) + return -EINVAL; + + do { + ret = tcs_mbox_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 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) { + for (j = 0; j < len; j++) { + if (tcs->cmd_cache[i] != cmd[0].addr) { + if (j == 0) + break; + WARN(tcs->cmd_cache[i + j] != cmd[j].addr, + "Message does not match previous sequence.\n"); + return -EINVAL; + } else if (j == len - 1) { + return i; + } + } + } + + return -ENODATA; +} + +static int find_slots(struct tcs_group *tcs, const struct tcs_request *msg, + int *m, int *n) +{ + 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; + *m = offset + tcs->offset; + *n = slot % tcs->ncpt; + + return 0; +} + +static int tcs_ctrl_write(struct rsc_drv *drv, const struct tcs_request *msg) +{ + struct tcs_group *tcs; + int m = 0, n = 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, &m, &n); + if (!ret) + __tcs_buffer_write(drv, m, n, 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) +{ + 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; + int i, ret, n, st = 0; + struct tcs_group *tcs; + struct resource *res; + void __iomem *base; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "drv"); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcs"); + drv->tcs_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(drv->tcs_base)) + return PTR_ERR(drv->tcs_base); + + 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; + + /* + * 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; + + 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; + + INIT_LIST_HEAD(&drv->response_pending); + spin_lock_init(&drv->drv_lock); + tasklet_init(&drv->tasklet, tcs_notify_tx_done, (unsigned long)drv); + bitmap_zero(drv->tcs_in_use, MAX_TCS_NR); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, tcs_irq_handler, + 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, 0, drv->tcs[ACTIVE_TCS].mask); + + dev_set_drvdata(&pdev->dev, drv); + + 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/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c new file mode 100644 index 000000000000..daadbb3f0494 --- /dev/null +++ b/drivers/soc/qcom/rpmh.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#include <linux/atomic.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/wait.h> + +#include <soc/qcom/rpmh.h> + +#include "rpmh-internal.h" + +#define RPMH_MAX_MBOXES 2 +#define RPMH_TIMEOUT_MS 10000 +#define RPMH_MAX_REQ_IN_BATCH 10 + +#define DEFINE_RPMH_MSG_ONSTACK(rc, s, q, name) \ + struct rpmh_request name = { \ + .msg = { \ + .state = s, \ + .cmds = name.cmd, \ + .num_cmds = 0, \ + .wait_for_compl = true, \ + }, \ + .cmd = { { 0 } }, \ + .completion = q, \ + .rc = rc, \ + .free = NULL, \ + .wait_count = NULL, \ + } + +/** + * 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 + * + * @msg: the request + * @cmd: the payload that will be part of the @msg + * @completion: triggered when request is done + * @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; + struct tcs_cmd cmd[MAX_RPMH_PAYLOAD]; + struct completion *completion; + struct rpmh_client *rc; + int err; + struct rpmh_request *free; + atomic_t *wait_count; +}; + +/** + * 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 + * @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[2 * RPMH_MAX_REQ_IN_BATCH]; +}; + +/** + * struct rpmh_client: the client object + * + * @dev: the platform device that is the owner + * @ctrlr: the controller associated with this client. + */ +struct rpmh_client { + struct device *dev; + struct rpmh_ctrlr *ctrlr; +}; + +static struct rpmh_ctrlr rpmh_rsc[RPMH_MAX_MBOXES]; +static DEFINE_MUTEX(rpmh_ctrlr_mutex); + +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; + + if (r) + dev_err(rpm_msg->rc->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) + return; + + if (wc && !atomic_dec_and_test(wc)) + return; + + complete(compl); +} +EXPORT_SYMBOL(rpmh_tx_done); + +/** + * wait_for_tx_done: Wait until the response is received. + * + * @rc: The RPMH client + * @compl: The completion object + * @addr: An addr that we sent in that request + * @data: The data for the address in that request + */ +static int wait_for_tx_done(struct rpmh_client *rc, + struct completion *compl, u32 addr, u32 data) +{ + int ret; + + might_sleep(); + + ret = wait_for_completion_timeout(compl, + msecs_to_jiffies(RPMH_TIMEOUT_MS)); + if (ret) + dev_dbg(rc->dev, + "RPMH response received addr=%#x data=%#x\n", + addr, data); + else + dev_err(rc->dev, + "RPMH response timeout addr=%#x data=%#x\n", + addr, data); + + return (ret > 0) ? 0 : -ETIMEDOUT; +} + +static struct cache_req *__find_req(struct rpmh_client *rc, u32 addr) +{ + struct cache_req *p, *req = NULL; + + list_for_each_entry(p, &rc->ctrlr->cache, list) { + if (p->addr == addr) { + req = p; + break; + } + } + + return req; +} + +static struct cache_req *cache_rpm_request(struct rpmh_client *rc, + enum rpmh_state state, + struct tcs_cmd *cmd) +{ + struct cache_req *req; + struct rpmh_ctrlr *rpm = rc->ctrlr; + unsigned long flags; + + spin_lock_irqsave(&rpm->lock, flags); + req = __find_req(rc, 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, &rpm->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; + }; + + rpm->dirty = true; +unlock: + spin_unlock_irqrestore(&rpm->lock, flags); + + return req; +} + +/** + * __rpmh_write: Cache and send the RPMH request + * + * @rc: The RPMH client + * @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(struct rpmh_client *rc, enum rpmh_state state, + struct rpmh_request *rpm_msg) +{ + int ret = -EINVAL; + struct cache_req *req; + int i; + + /* Cache the request in our store and link the payload */ + for (i = 0; i < rpm_msg->msg.num_cmds; i++) { + req = cache_rpm_request(rc, state, &rpm_msg->msg.cmds[i]); + if (IS_ERR(req)) + return PTR_ERR(req); + } + + rpm_msg->msg.state = state; + + if (state == RPMH_ACTIVE_ONLY_STATE) { + WARN_ON(irqs_disabled()); + ret = rpmh_rsc_send_data(rc->ctrlr->drv, &rpm_msg->msg); + if (!ret) + dev_dbg(rc->dev, + "RPMH request sent addr=%#x, data=%i#x\n", + rpm_msg->msg.cmds[0].addr, + rpm_msg->msg.cmds[0].data); + else + dev_warn(rc->dev, + "Error in RPMH request addr=%#x, data=%#x\n", + rpm_msg->msg.cmds[0].addr, + rpm_msg->msg.cmds[0].data); + } else { + ret = rpmh_rsc_write_ctrl_data(rc->ctrlr->drv, &rpm_msg->msg); + /* Clean up our call by spoofing tx_done */ + rpmh_tx_done(&rpm_msg->msg, ret); + } + + return ret; +} + +static struct rpmh_request *__get_rpmh_msg_async(struct rpmh_client *rc, + enum rpmh_state state, + const struct tcs_cmd *cmd, + u32 n) +{ + struct rpmh_request *req; + + if (IS_ERR_OR_NULL(rc) || !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 + * + * @rc: The RPMh handle got from rpmh_get_client + * @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(struct rpmh_client *rc, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + struct rpmh_request *rpm_msg; + + rpm_msg = __get_rpmh_msg_async(rc, state, cmd, n); + if (IS_ERR(rpm_msg)) + return PTR_ERR(rpm_msg); + + return __rpmh_write(rc, state, rpm_msg); +} +EXPORT_SYMBOL(rpmh_write_async); + +/** + * 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(struct rpmh_client *rc, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + DECLARE_COMPLETION_ONSTACK(compl); + DEFINE_RPMH_MSG_ONSTACK(rc, state, &compl, rpm_msg); + int ret; + + if (IS_ERR_OR_NULL(rc) || !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(rc, state, &rpm_msg); + if (ret) + return ret; + + return wait_for_tx_done(rc, &compl, cmd[0].addr, cmd[0].data); +} +EXPORT_SYMBOL(rpmh_write); + +static int cache_batch(struct rpmh_client *rc, + struct rpmh_request **rpm_msg, int count) +{ + struct rpmh_ctrlr *rpm = rc->ctrlr; + unsigned long flags; + int ret = 0; + int index = 0; + int i; + + spin_lock_irqsave(&rpm->lock, flags); + while (rpm->batch_cache[index]) + index++; + if (index + count >= 2 * RPMH_MAX_REQ_IN_BATCH) { + ret = -ENOMEM; + goto fail; + } + + for (i = 0; i < count; i++) + rpm->batch_cache[index + i] = rpm_msg[i]; +fail: + spin_unlock_irqrestore(&rpm->lock, flags); + + return ret; +} + +static int flush_batch(struct rpmh_client *rc) +{ + struct rpmh_ctrlr *rpm = rc->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(&rpm->lock, flags); + for (i = 0; rpm->batch_cache[i]; i++) { + rpm_msg = rpm->batch_cache[i]; + ret = rpmh_rsc_write_ctrl_data(rc->ctrlr->drv, &rpm_msg->msg); + if (ret) + break; + } + spin_unlock_irqrestore(&rpm->lock, flags); + + return ret; +} + +static void invalidate_batch(struct rpmh_client *rc) +{ + struct rpmh_ctrlr *rpm = rc->ctrlr; + unsigned long flags; + int index = 0; + int i; + + spin_lock_irqsave(&rpm->lock, flags); + while (rpm->batch_cache[index]) + index++; + for (i = 0; i < index; i++) { + kfree(rpm->batch_cache[i]->free); + rpm->batch_cache[i] = NULL; + } + spin_unlock_irqrestore(&rpm->lock, flags); +} + +/** + * rpmh_write_batch: Write multiple sets of RPMH commands and wait for the + * batch to finish. + * + * @rc: The RPMh handle got from rpmh_get_client + * @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 mailbox 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(struct rpmh_client *rc, 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); + int count = 0; + int ret, i; + + if (IS_ERR_OR_NULL(rc) || !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(rc, 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(rc, 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(rc->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; + } + } + + return wait_for_tx_done(rc, &compl, cmd[0].addr, cmd[0].data); +} +EXPORT_SYMBOL(rpmh_write_batch); + +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(struct rpmh_client *rc, enum rpmh_state state, + u32 addr, u32 data) +{ + DEFINE_RPMH_MSG_ONSTACK(rc, state, NULL, rpm_msg); + + /* 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(rc->ctrlr->drv, &rpm_msg.msg); +} + +/** + * rpmh_flush: Flushes the buffered active and sleep sets to TCS + * + * @rc: The RPMh handle got from rpmh_get_client + * + * 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(struct rpmh_client *rc) +{ + struct cache_req *p; + struct rpmh_ctrlr *rpm = rc->ctrlr; + int ret; + + if (IS_ERR_OR_NULL(rc)) + return -EINVAL; + + if (!rpm->dirty) { + pr_debug("Skipping flush, TCS has latest data.\n"); + return 0; + } + + /* First flush the cached batch requests */ + ret = flush_batch(rc); + if (ret) + return ret; + + /* + * Nobody else should be calling this function other than system PM,, + * hence we can run without locks. + */ + list_for_each_entry(p, &rc->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(rc, RPMH_SLEEP_STATE, p->addr, p->sleep_val); + if (ret) + return ret; + ret = send_single(rc, RPMH_WAKE_ONLY_STATE, + p->addr, p->wake_val); + if (ret) + return ret; + } + + rpm->dirty = false; + + return 0; +} +EXPORT_SYMBOL(rpmh_flush); + +/** + * rpmh_invalidate: Invalidate all sleep and active sets + * sets. + * + * @rc: The RPMh handle got from rpmh_get_client + * + * Invalidate the sleep and active values in the TCS blocks. + */ +int rpmh_invalidate(struct rpmh_client *rc) +{ + struct rpmh_ctrlr *rpm = rc->ctrlr; + int ret; + + if (IS_ERR_OR_NULL(rc)) + return -EINVAL; + + invalidate_batch(rc); + + rpm->dirty = true; + + do { + ret = rpmh_rsc_invalidate(rc->ctrlr->drv); + } while (ret == -EAGAIN); + + return ret; +} +EXPORT_SYMBOL(rpmh_invalidate); + +static struct rpmh_ctrlr *get_rpmh_ctrlr(struct platform_device *pdev) +{ + int i; + struct rsc_drv *drv = dev_get_drvdata(pdev->dev.parent); + struct rpmh_ctrlr *ctrlr = ERR_PTR(-EINVAL); + + if (!drv) + return ctrlr; + + mutex_lock(&rpmh_ctrlr_mutex); + for (i = 0; i < RPMH_MAX_MBOXES; i++) { + if (rpmh_rsc[i].drv == drv) { + ctrlr = &rpmh_rsc[i]; + goto unlock; + } + } + + for (i = 0; i < RPMH_MAX_MBOXES; i++) { + if (rpmh_rsc[i].drv == NULL) { + ctrlr = &rpmh_rsc[i]; + ctrlr->drv = drv; + spin_lock_init(&ctrlr->lock); + INIT_LIST_HEAD(&ctrlr->cache); + break; + } + } + WARN_ON(i == RPMH_MAX_MBOXES); +unlock: + mutex_unlock(&rpmh_ctrlr_mutex); + return ctrlr; +} + +/** + * rpmh_get_client: Get the RPMh handle + * + * @pdev: the platform device which needs to communicate with RPM + * accelerators + * May sleep. + */ +struct rpmh_client *rpmh_get_client(struct platform_device *pdev) +{ + struct rpmh_client *rc; + + rc = kzalloc(sizeof(*rc), GFP_KERNEL); + if (!rc) + return ERR_PTR(-ENOMEM); + + rc->dev = &pdev->dev; + rc->ctrlr = get_rpmh_ctrlr(pdev); + if (IS_ERR(rc->ctrlr)) { + kfree(rc); + return ERR_PTR(-EINVAL); + } + + return rc; +} +EXPORT_SYMBOL(rpmh_get_client); + +/** + * rpmh_release: Release the RPMH client + * + * @rc: The RPMh handle to be freed. + */ +void rpmh_release(struct rpmh_client *rc) +{ + kfree(rc); +} +EXPORT_SYMBOL(rpmh_release); diff --git a/drivers/soc/qcom/trace-rpmh.h b/drivers/soc/qcom/trace-rpmh.h new file mode 100644 index 000000000000..8d7326e684db --- /dev/null +++ b/drivers/soc/qcom/trace-rpmh.h @@ -0,0 +1,89 @@ +/* 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 <linux/tracepoint.h> +#include "rpmh-internal.h" + +DECLARE_EVENT_CLASS(rpmh_notify, + + TP_PROTO(struct rsc_drv *d, struct tcs_response *r), + + TP_ARGS(d, r), + + TP_STRUCT__entry( + __string(name, d->name) + __field(int, m) + __field(u32, addr) + __field(int, errno) + ), + + TP_fast_assign( + __assign_str(name, d->name); + __entry->m = r->m; + __entry->addr = r->msg->cmds[0].addr; + __entry->errno = r->err; + ), + + TP_printk("%s: ack: tcs-m:%d addr: %#x errno: %d", + __get_str(name), __entry->m, __entry->addr, __entry->errno) +); + +DEFINE_EVENT(rpmh_notify, rpmh_notify_irq, + TP_PROTO(struct rsc_drv *d, struct tcs_response *r), + TP_ARGS(d, r) +); + +DEFINE_EVENT(rpmh_notify, rpmh_notify_tx_done, + TP_PROTO(struct rsc_drv *d, struct tcs_response *r), + TP_ARGS(d, r) +); + + +TRACE_EVENT(rpmh_send_msg, + + TP_PROTO(struct rsc_drv *d, int m, int n, u32 h, 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 <trace/define_trace.h> 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 000000000000..868f998ea998 --- /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/rpmh.h b/include/soc/qcom/rpmh.h new file mode 100644 index 000000000000..a5d5c57a4329 --- /dev/null +++ b/include/soc/qcom/rpmh.h @@ -0,0 +1,60 @@ +/* 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 <soc/qcom/tcs.h> +#include <linux/platform_device.h> + +struct rpmh_client; + +#if IS_ENABLED(CONFIG_QCOM_RPMH) +int rpmh_write(struct rpmh_client *rc, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n); + +int rpmh_write_async(struct rpmh_client *rc, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n); + +int rpmh_write_batch(struct rpmh_client *rc, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 *n); + +struct rpmh_client *rpmh_get_client(struct platform_device *pdev); + +int rpmh_flush(struct rpmh_client *rc); + +int rpmh_invalidate(struct rpmh_client *rc); + +void rpmh_release(struct rpmh_client *rc); + +#else + +static inline int rpmh_write(struct rpmh_client *rc, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ return -ENODEV; } + +static inline struct rpmh_client *rpmh_get_client(struct platform_device *pdev) +{ return ERR_PTR(-ENODEV); } + +static inline int rpmh_write_async(struct rpmh_client *rc, + enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ return -ENODEV; } + +static inline int rpmh_write_batch(struct rpmh_client *rc, + enum rpmh_state state, + const struct tcs_cmd *cmd, u32 *n) +{ return -ENODEV; } + +static inline int rpmh_flush(struct rpmh_client *rc) +{ return -ENODEV; } + +static inline int rpmh_invalidate(struct rpmh_client *rc) +{ return -ENODEV; } + +static inline void rpmh_release(struct rpmh_client *rc) { } +#endif /* CONFIG_QCOM_RPMH */ + +#endif /* __SOC_QCOM_RPMH_H__ */ diff --git a/include/soc/qcom/tcs.h b/include/soc/qcom/tcs.h new file mode 100644 index 000000000000..4b78f881010a --- /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; + bool 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; + bool wait_for_compl; + u32 num_cmds; + struct tcs_cmd *cmds; +}; + +#endif /* __SOC_QCOM_TCS_H__ */ |