diff mbox series

[2/3] soc: qcom: rpmh and cmd-db drivers

Message ID 20240617-b4-qcom-rpmh-v1-2-bd2336923e0a@linaro.org
State New
Headers show
Series qcom: rpmh core and regulator support | expand

Commit Message

Caleb Connolly June 17, 2024, 8:32 a.m. UTC
Introduce two Qualcomm SoC drivers, the RPMh and cmd-db. RPMh is a the
name for the second generation Resource Power Management hub on Qualcomm
SoCs. Most core regulators have to be controlled via this hub.

The cmd-db is a region of memory which contains offsets and data about
how to communicate with the RPMh.

Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
---
 drivers/soc/Kconfig              |   1 +
 drivers/soc/Makefile             |   1 +
 drivers/soc/qcom/Kconfig         |  25 ++
 drivers/soc/qcom/Makefile        |   4 +
 drivers/soc/qcom/cmd-db.c        | 246 ++++++++++++++++
 drivers/soc/qcom/rpmh-internal.h | 141 +++++++++
 drivers/soc/qcom/rpmh-rsc.c      | 619 +++++++++++++++++++++++++++++++++++++++
 drivers/soc/qcom/rpmh.c          | 110 +++++++
 include/soc/qcom/cmd-db.h        |  42 +++
 include/soc/qcom/rpmh.h          |  29 ++
 include/soc/qcom/tcs.h           |  78 +++++
 11 files changed, 1296 insertions(+)

Comments

Neil Armstrong June 17, 2024, 9:53 a.m. UTC | #1
On 17/06/2024 10:32, Caleb Connolly wrote:
> Introduce two Qualcomm SoC drivers, the RPMh and cmd-db. RPMh is a the
> name for the second generation Resource Power Management hub on Qualcomm
> SoCs. Most core regulators have to be controlled via this hub.
> 
> The cmd-db is a region of memory which contains offsets and data about
> how to communicate with the RPMh.
> 
> Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
> ---
>   drivers/soc/Kconfig              |   1 +
>   drivers/soc/Makefile             |   1 +
>   drivers/soc/qcom/Kconfig         |  25 ++
>   drivers/soc/qcom/Makefile        |   4 +
>   drivers/soc/qcom/cmd-db.c        | 246 ++++++++++++++++
>   drivers/soc/qcom/rpmh-internal.h | 141 +++++++++
>   drivers/soc/qcom/rpmh-rsc.c      | 619 +++++++++++++++++++++++++++++++++++++++
>   drivers/soc/qcom/rpmh.c          | 110 +++++++
>   include/soc/qcom/cmd-db.h        |  42 +++
>   include/soc/qcom/rpmh.h          |  29 ++
>   include/soc/qcom/tcs.h           |  78 +++++
>   11 files changed, 1296 insertions(+)
> 

<snip>

> +
> +	if (drv->ver.major == 3) {
> +		printf("RPMh v3 not supported\n");
> +		return -EOPNOTSUPP;
> +	} else {
> +		drv->regs = rpmh_rsc_reg_offset_ver_2_7;
> +	}


I think you can safely add the v3 offsets:

==========><======================================
diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c
index 29fc0c2ed49..a4ff374675d 100644
--- a/drivers/soc/qcom/rpmh-rsc.c
+++ b/drivers/soc/qcom/rpmh-rsc.c
@@ -152,6 +152,24 @@ static u32 rpmh_rsc_reg_offset_ver_2_7[] = {
  	[RSC_DRV_CMD_RESP_DATA]		= 0x40,
  };

+static u32 rpmh_rsc_reg_offset_ver_3_0[] = {
+	[RSC_DRV_TCS_OFFSET]		= 672,
+	[RSC_DRV_CMD_OFFSET]		= 24,
+	[DRV_SOLVER_CONFIG]		= 0x04,
+	[DRV_PRNT_CHLD_CONFIG]		= 0x0C,
+	[RSC_DRV_IRQ_ENABLE]		= 0x00,
+	[RSC_DRV_IRQ_STATUS]		= 0x04,
+	[RSC_DRV_IRQ_CLEAR]		= 0x08,
+	[RSC_DRV_CMD_WAIT_FOR_CMPL]	= 0x20,
+	[RSC_DRV_CONTROL]		= 0x24,
+	[RSC_DRV_STATUS]		= 0x28,
+	[RSC_DRV_CMD_ENABLE]		= 0x2C,
+	[RSC_DRV_CMD_MSGID]		= 0x34,
+	[RSC_DRV_CMD_ADDR]		= 0x38,
+	[RSC_DRV_CMD_DATA]		= 0x3C,
+	[RSC_DRV_CMD_STATUS]		= 0x40,
+	[RSC_DRV_CMD_RESP_DATA]		= 0x44,
+};

  static inline void __iomem *
  tcs_reg_addr(const struct rsc_drv *drv, int reg, int tcs_id)
@@ -573,10 +591,9 @@ found:
  	drv->ver.minor = rsc_id & (MINOR_VER_MASK << MINOR_VER_SHIFT);
  	drv->ver.minor >>= MINOR_VER_SHIFT;

-	if (drv->ver.major == 3) {
-		printf("RPMh v3 not supported\n");
-		return -ENOTSUPP;
-	} else
+	if (drv->ver.major == 3)
+		drv->regs = rpmh_rsc_reg_offset_ver_3_0;
+	else
  		drv->regs = rpmh_rsc_reg_offset_ver_2_7;

  	ret = rpmh_probe_tcs_config(dev, drv);
==========><======================================

And add my:
Tested-by: Neil Armstrong <neil.armstrong@linaro.org> # on SM8550 & SM8^%)


<snip>
Sumit Garg June 21, 2024, 8:25 a.m. UTC | #2
Hi Caleb,

On Mon, 17 Jun 2024 at 14:02, Caleb Connolly <caleb.connolly@linaro.org> wrote:
>
> Introduce two Qualcomm SoC drivers, the RPMh and cmd-db. RPMh is a the
> name for the second generation Resource Power Management hub on Qualcomm
> SoCs. Most core regulators have to be controlled via this hub.
>
> The cmd-db is a region of memory which contains offsets and data about
> how to communicate with the RPMh.
>
> Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
> ---
>  drivers/soc/Kconfig              |   1 +
>  drivers/soc/Makefile             |   1 +
>  drivers/soc/qcom/Kconfig         |  25 ++
>  drivers/soc/qcom/Makefile        |   4 +
>  drivers/soc/qcom/cmd-db.c        | 246 ++++++++++++++++
>  drivers/soc/qcom/rpmh-internal.h | 141 +++++++++
>  drivers/soc/qcom/rpmh-rsc.c      | 619 +++++++++++++++++++++++++++++++++++++++
>  drivers/soc/qcom/rpmh.c          | 110 +++++++
>  include/soc/qcom/cmd-db.h        |  42 +++
>  include/soc/qcom/rpmh.h          |  29 ++
>  include/soc/qcom/tcs.h           |  78 +++++
>  11 files changed, 1296 insertions(+)
>
> diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
> index 03433bc0e6d2..8ceca0cb1386 100644
> --- a/drivers/soc/Kconfig
> +++ b/drivers/soc/Kconfig
> @@ -39,8 +39,9 @@ config SOC_XILINX_VERSAL_NET
>           Enable this option to select SoC device id driver for Xilinx Versal NET.
>           This allows other drivers to verify the SoC familiy & revision using
>           matching SoC attributes.
>
> +source "drivers/soc/qcom/Kconfig"
>  source "drivers/soc/samsung/Kconfig"
>  source "drivers/soc/ti/Kconfig"
>
>  endmenu
> diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
> index 610bf816d40a..c3d484e5bf6c 100644
> --- a/drivers/soc/Makefile
> +++ b/drivers/soc/Makefile
> @@ -3,8 +3,9 @@
>  # Makefile for the U-Boot SOC specific device drivers.
>
>  obj-$(CONFIG_SOC_SAMSUNG) += samsung/
>  obj-$(CONFIG_SOC_TI) += ti/
> +obj-$(CONFIG_SOC_QCOM) += qcom/
>  obj-$(CONFIG_SOC_DEVICE) += soc-uclass.o
>  obj-$(CONFIG_SOC_DEVICE_TI_K3) += soc_ti_k3.o
>  obj-$(CONFIG_SANDBOX) += soc_sandbox.o
>  obj-$(CONFIG_SOC_XILINX_ZYNQMP) += soc_xilinx_zynqmp.o
> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> new file mode 100644
> index 000000000000..a0872c5b3c83
> --- /dev/null
> +++ b/drivers/soc/qcom/Kconfig
> @@ -0,0 +1,25 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +
> +menuconfig SOC_QCOM
> +       bool "Qualcomm SOC drivers support"
> +       help
> +         Say Y here if you want to enable Qualcomm SOC drivers support.
> +
> +if SOC_QCOM
> +
> +config QCOM_COMMAND_DB
> +       bool "Qualcomm Command DB"
> +       help
> +         Command DB queries shared memory by key string for shared system
> +         resources. Platform drivers that require to set state of a shared
> +         resource on a RPM-hardened platform must use this database to get
> +         SoC specific identifier and information for the shared resources.
> +
> +config QCOM_RPMH
> +       bool "Qualcomm RPMh support"
> +       depends on QCOM_COMMAND_DB
> +       help
> +         Say y here to support the Qualcomm RPMh (resource peripheral manager)
> +         if you need to control regulators on Qualcomm platforms, say y here.
> +
> +endif # SOC_QCOM
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> new file mode 100644
> index 000000000000..4fca569cfb77
> --- /dev/null
> +++ b/drivers/soc/qcom/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +
> +obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
> +obj-$(CONFIG_QCOM_RPMH)        += rpmh-rsc.o rpmh.o
> diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c
> new file mode 100644
> index 000000000000..7bfd72ae2f3f
> --- /dev/null
> +++ b/drivers/soc/qcom/cmd-db.c
> @@ -0,0 +1,246 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved.
> + * Copyright (c) 2023, Linaro Ltd.
> + */
> +
> +#include <dm/device.h>
> +#include <dm.h>
> +#include <dm/device_compat.h>
> +#include <linux/kernel.h>
> +#include <linux/types.h>
> +#include <linux/ioport.h>
> +#include <linux/byteorder/generic.h>
> +
> +#include <soc/qcom/cmd-db.h>
> +
> +#define NUM_PRIORITY           2
> +#define MAX_SLV_ID             8
> +#define SLAVE_ID_MASK          0x7
> +#define SLAVE_ID_SHIFT         16
> +
> +/**
> + * struct entry_header: header for each entry in cmddb
> + *
> + * @id: resource's identifier
> + * @priority: unused
> + * @addr: the address of the resource
> + * @len: length of the data
> + * @offset: offset from :@data_offset, start of the data
> + */
> +struct entry_header {
> +       u8 id[8];
> +       __le32 priority[NUM_PRIORITY];
> +       __le32 addr;
> +       __le16 len;
> +       __le16 offset;
> +};
> +
> +/**
> + * struct rsc_hdr: resource header information
> + *
> + * @slv_id: id for the resource
> + * @header_offset: entry's header at offset from the end of the cmd_db_header
> + * @data_offset: entry's data at offset from the end of the cmd_db_header
> + * @cnt: number of entries for HW type
> + * @version: MSB is major, LSB is minor
> + * @reserved: reserved for future use.
> + */
> +struct rsc_hdr {
> +       __le16 slv_id;
> +       __le16 header_offset;
> +       __le16 data_offset;
> +       __le16 cnt;
> +       __le16 version;
> +       __le16 reserved[3];
> +};
> +
> +/**
> + * struct cmd_db_header: The DB header information
> + *
> + * @version: The cmd db version
> + * @magic: constant expected in the database
> + * @header: array of resources
> + * @checksum: checksum for the header. Unused.
> + * @reserved: reserved memory
> + * @data: driver specific data
> + */
> +struct cmd_db_header {
> +       __le32 version;
> +       u8 magic[4];
> +       struct rsc_hdr header[MAX_SLV_ID];
> +       __le32 checksum;
> +       __le32 reserved;
> +       u8 data[];
> +};
> +
> +/**
> + * DOC: Description of the Command DB database.
> + *
> + * At the start of the command DB memory is the cmd_db_header structure.
> + * The cmd_db_header holds the version, checksum, magic key as well as an
> + * array for header for each slave (depicted by the rsc_header). Each h/w
> + * based accelerator is a 'slave' (shared resource) and has slave id indicating
> + * the type of accelerator. The rsc_header is the header for such individual
> + * slaves of a given type. The entries for each of these slaves begin at the
> + * rsc_hdr.header_offset. In addition each slave could have auxiliary data
> + * that may be needed by the driver. The data for the slave starts at the
> + * entry_header.offset to the location pointed to by the rsc_hdr.data_offset.
> + *
> + * Drivers have a stringified key to a slave/resource. They can query the slave
> + * information and get the slave id and the auxiliary data and the length of the
> + * data. Using this information, they can format the request to be sent to the
> + * h/w accelerator and request a resource state.
> + */
> +
> +static const u8 CMD_DB_MAGIC[] = { 0xdb, 0x30, 0x03, 0x0c };
> +
> +static bool cmd_db_magic_matches(const struct cmd_db_header *header)
> +{
> +       const u8 *magic = header->magic;
> +
> +       return memcmp(magic, CMD_DB_MAGIC, ARRAY_SIZE(CMD_DB_MAGIC)) == 0;
> +}
> +
> +static struct cmd_db_header *cmd_db_header;
> +
> +static inline const void *rsc_to_entry_header(const struct rsc_hdr *hdr)
> +{
> +       u16 offset = le16_to_cpu(hdr->header_offset);
> +
> +       return cmd_db_header->data + offset;
> +}
> +
> +static inline void *
> +rsc_offset(const struct rsc_hdr *hdr, const struct entry_header *ent)
> +{
> +       u16 offset = le16_to_cpu(hdr->data_offset);
> +       u16 loffset = le16_to_cpu(ent->offset);
> +
> +       return cmd_db_header->data + offset + loffset;
> +}
> +
> +static int cmd_db_get_header(const char *id, const struct entry_header **eh,
> +                            const struct rsc_hdr **rh)
> +{
> +       const struct rsc_hdr *rsc_hdr;
> +       const struct entry_header *ent;
> +       int i, j;
> +       u8 query[sizeof(ent->id)] __nonstring;
> +
> +       /*
> +        * Pad out query string to same length as in DB. NOTE: the output
> +        * query string is not necessarily '\0' terminated if it bumps up
> +        * against the max size. That's OK and expected.
> +        */
> +       strncpy(query, id, sizeof(query));
> +
> +       for (i = 0; i < MAX_SLV_ID; i++) {
> +               rsc_hdr = &cmd_db_header->header[i];
> +               if (!rsc_hdr->slv_id)
> +                       break;
> +
> +               ent = rsc_to_entry_header(rsc_hdr);
> +               for (j = 0; j < le16_to_cpu(rsc_hdr->cnt); j++, ent++) {
> +                       if (memcmp(ent->id, query, sizeof(ent->id)) == 0) {
> +                               if (eh)
> +                                       *eh = ent;
> +                               if (rh)
> +                                       *rh = rsc_hdr;
> +                               return 0;
> +                       }
> +               }
> +       }
> +
> +       return -ENODEV;
> +}
> +
> +/**
> + * cmd_db_read_addr() - Query command db for resource id address.
> + *
> + * @id: resource id to query for address
> + *
> + * Return: resource address on success, 0 on error
> + *
> + * This is used to retrieve resource address based on resource
> + * id.
> + */
> +u32 cmd_db_read_addr(const char *id)
> +{
> +       int ret;
> +       const struct entry_header *ent;
> +
> +       ret = cmd_db_get_header(id, &ent, NULL);
> +
> +       return ret < 0 ? 0 : le32_to_cpu(ent->addr);
> +}
> +EXPORT_SYMBOL(cmd_db_read_addr);

Export symbols aren't required in U-Boot here and other instances in this patch.

> +
> +/**
> + * cmd_db_read_aux_data() - Query command db for aux data.
> + *
> + *  @id: Resource to retrieve AUX Data on
> + *  @len: size of data buffer returned
> + *
> + *  Return: pointer to data on success, error pointer otherwise
> + */
> +const void *cmd_db_read_aux_data(const char *id, size_t *len)
> +{
> +       int ret;
> +       const struct entry_header *ent;
> +       const struct rsc_hdr *rsc_hdr;
> +
> +       ret = cmd_db_get_header(id, &ent, &rsc_hdr);
> +       if (ret)
> +               return ERR_PTR(ret);
> +
> +       if (len)
> +               *len = le16_to_cpu(ent->len);
> +
> +       return rsc_offset(rsc_hdr, ent);
> +}
> +EXPORT_SYMBOL(cmd_db_read_aux_data);
> +
> +/**
> + * cmd_db_read_slave_id - Get the slave ID for a given resource address
> + *
> + * @id: Resource id to query the DB for version
> + *
> + * Return: cmd_db_hw_type enum on success, CMD_DB_HW_INVALID on error
> + */
> +enum cmd_db_hw_type cmd_db_read_slave_id(const char *id)
> +{
> +       int ret;
> +       const struct entry_header *ent;
> +       u32 addr;
> +
> +       ret = cmd_db_get_header(id, &ent, NULL);
> +       if (ret < 0)
> +               return CMD_DB_HW_INVALID;
> +
> +       addr = le32_to_cpu(ent->addr);
> +       return (addr >> SLAVE_ID_SHIFT) & SLAVE_ID_MASK;
> +}
> +EXPORT_SYMBOL(cmd_db_read_slave_id);
> +
> +int cmd_db_init(ofnode node)
> +{
> +       void __iomem *base;
> +
> +       debug("%s(%s)\n", __func__, ofnode_get_name(node));
> +
> +       base = (void __iomem *)ofnode_get_addr(node);
> +       if ((fdt_addr_t)base == FDT_ADDR_T_NONE) {
> +               log_err("%s: Failed to read base address\n", __func__);
> +               return -ENOENT;
> +       }
> +
> +       cmd_db_header = base;
> +       if (!cmd_db_magic_matches(cmd_db_header)) {
> +               log_err("%s: Invalid Command DB Magic\n", __func__);
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(cmd_db_init);
> diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h
> new file mode 100644
> index 000000000000..9dbb1c51cc35
> --- /dev/null
> +++ b/drivers/soc/qcom/rpmh-internal.h
> @@ -0,0 +1,141 @@
> +// 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)
> +
> +#define USEC_PER_SEC           1000000UL
> +
> +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.
> + * @req:       Requests that are sent from the TCS; only used for ACTIVE_ONLY
> + *             transfers (could be on a wake/sleep TCS if we are borrowing for
> + *             an ACTIVE_ONLY transfer).
> + *             Start: grab drv->lock, set req, set tcs_in_use, drop drv->lock,
> + *                    trigger
> + *             End: get irq, access req,
> + *                  grab drv->lock, clear tcs_in_use, drop drv->lock
> + * @slots:     Indicates which of @cmd_addr are occupied; only used for
> + *             SLEEP / WAKE TCSs.  Things are tightly packed in the
> + *             case that (ncpt < MAX_CMDS_PER_TCS).  That is if ncpt = 2 and
> + *             MAX_CMDS_PER_TCS = 16 then bit[2] = the first bit in 2nd TCS.
> + */
> +struct tcs_group {
> +       struct rsc_drv *drv;
> +       int type;
> +       u32 mask;
> +       u32 offset;
> +       int num_tcs;
> +       int ncpt;
> +       const struct tcs_request *req[MAX_TCS_PER_TYPE];
> +       DECLARE_BITMAP(slots, MAX_TCS_SLOTS);
> +};
> +
> +/**
> + * 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
> + * @needs_free: check to free dynamically allocated request object
> + */
> +struct rpmh_request {
> +       struct tcs_request msg;
> +       struct tcs_cmd cmd[MAX_RPMH_PAYLOAD];
> +       const struct udevice *dev;
> +       bool needs_free;
> +};
> +
> +/**
> + * struct rpmh_ctrlr: our representation of the controller
> + *
> + * @cache: the list of cached requests
> + * @cache_lock: synchronize access to the cache data
> + * @dirty: was the cache updated since flush
> + * @batch_cache: Cache sleep and wake requests sent as batch
> + */
> +struct rpmh_ctrlr {
> +       struct list_head cache;
> +       bool dirty;
> +       struct list_head batch_cache;
> +};
> +
> +struct rsc_ver {
> +       u32 major;
> +       u32 minor;
> +};
> +
> +/**
> + * struct rsc_drv: the Direct Resource Voter (DRV) of the
> + * Resource State Coordinator controller (RSC)
> + *
> + * @name:               Controller identifier.
> + * @base:               Start address of the DRV registers in this controller.
> + * @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.
> + * @rsc_pm:             CPU PM notifier for controller.
> + *                      Used when solver mode is not present.
> + * @cpus_in_pm:         Number of CPUs not in idle power collapse.
> + *                      Used when solver mode and "power-domains" is not present.
> + * @genpd_nb:           PM Domain notifier for cluster genpd notifications.
> + * @tcs:                TCS groups.
> + * @tcs_in_use:         S/W state of the TCS; only set for ACTIVE_ONLY
> + *                      transfers, but might show a sleep/wake TCS in use if
> + *                      it was borrowed for an active_only transfer.  You
> + *                      must hold the lock in this struct (AKA drv->lock) in
> + *                      order to update this.
> + * @lock:               Synchronize state of the controller.  If RPMH's cache
> + *                      lock will also be held, the order is: drv->lock then
> + *                      cache_lock.
> + * @tcs_wait:           Wait queue used to wait for @tcs_in_use to free up a
> + *                      slot
> + * @client:             Handle to the DRV's client.
> + * @dev:                RSC device.
> + */
> +struct rsc_drv {
> +       const char *name;
> +       void __iomem *base;
> +       void __iomem *tcs_base;
> +       int id;
> +       int num_tcs;
> +       struct tcs_group tcs[TCS_TYPE_NR];
> +       DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR);
> +       struct rpmh_ctrlr client;
> +       struct udevice *dev;
> +       struct rsc_ver ver;
> +       u32 *regs;
> +};
> +
> +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);
> +void rpmh_rsc_invalidate(struct rsc_drv *drv);
> +void rpmh_rsc_write_next_wakeup(struct rsc_drv *drv);
> +
> +int rpmh_rsc_wait_for_resp(struct rsc_drv *drv, int tcs_id);
> +int rpmh_flush(struct rpmh_ctrlr *ctrlr);
> +
> +#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..3c73dc0ffe25
> --- /dev/null
> +++ b/drivers/soc/qcom/rpmh-rsc.c
> @@ -0,0 +1,619 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved.
> + * Copyright (c) 2023, Linaro Ltd.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/types.h>
> +#include <dm.h>
> +#include <dm/ofnode.h>
> +#include <dm/devres.h>
> +#include <dm/device_compat.h>
> +#include <linux/delay.h>
> +#include <dm/lists.h>
> +#include <errno.h>
> +#include <asm/io.h>
> +#include <asm/bitops.h>
> +#include <linux/bitmap.h>
> +#include <log.h>
> +
> +#include <dt-bindings/soc/qcom,rpmh-rsc.h>
> +#include "rpmh-internal.h"
> +
> +#include <soc/qcom/rpmh.h>
> +#include <soc/qcom/cmd-db.h>
> +
> +#define RSC_DRV_ID                     0
> +
> +#define MAJOR_VER_MASK                 0xFF
> +#define MAJOR_VER_SHIFT                        16
> +#define MINOR_VER_MASK                 0xFF
> +#define MINOR_VER_SHIFT                        8
> +
> +enum {
> +       RSC_DRV_TCS_OFFSET,
> +       RSC_DRV_CMD_OFFSET,
> +       DRV_SOLVER_CONFIG,
> +       DRV_PRNT_CHLD_CONFIG,
> +       RSC_DRV_IRQ_ENABLE,
> +       RSC_DRV_IRQ_STATUS,
> +       RSC_DRV_IRQ_CLEAR,
> +       RSC_DRV_CMD_WAIT_FOR_CMPL,
> +       RSC_DRV_CONTROL,
> +       RSC_DRV_STATUS,
> +       RSC_DRV_CMD_ENABLE,
> +       RSC_DRV_CMD_MSGID,
> +       RSC_DRV_CMD_ADDR,
> +       RSC_DRV_CMD_DATA,
> +       RSC_DRV_CMD_STATUS,
> +       RSC_DRV_CMD_RESP_DATA,
> +};
> +
> +/* DRV HW Solver Configuration Information Register */
> +#define DRV_HW_SOLVER_MASK             1
> +#define DRV_HW_SOLVER_SHIFT            24
> +
> +/* DRV TCS Configuration Information Register */
> +#define DRV_NUM_TCS_MASK               0x3F
> +#define DRV_NUM_TCS_SHIFT              6
> +#define DRV_NCPT_MASK                  0x1F
> +#define DRV_NCPT_SHIFT                 27
> +
> +/* Offsets for CONTROL TCS Registers */
> +#define RSC_DRV_CTL_TCS_DATA_HI                0x38
> +#define RSC_DRV_CTL_TCS_DATA_HI_MASK   0xFFFFFF
> +#define RSC_DRV_CTL_TCS_DATA_HI_VALID  BIT(31)
> +#define RSC_DRV_CTL_TCS_DATA_LO                0x40
> +#define RSC_DRV_CTL_TCS_DATA_LO_MASK   0xFFFFFFFF
> +#define RSC_DRV_CTL_TCS_DATA_SIZE      32
> +
> +#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)
> +
> +/*
> + * Here's a high level overview of how all the registers in RPMH work
> + * together:
> + *
> + * - The main rpmh-rsc address is the base of a register space that can
> + *   be used to find overall configuration of the hardware
> + *   (DRV_PRNT_CHLD_CONFIG). Also found within the rpmh-rsc register
> + *   space are all the TCS blocks. The offset of the TCS blocks is
> + *   specified in the device tree by "qcom,tcs-offset" and used to
> + *   compute tcs_base.
> + * - TCS blocks come one after another. Type, count, and order are
> + *   specified by the device tree as "qcom,tcs-config".
> + * - Each TCS block has some registers, then space for up to 16 commands.
> + *   Note that though address space is reserved for 16 commands, fewer
> + *   might be present. See ncpt (num cmds per TCS).
> + *
> + * Here's a picture:
> + *
> + *  +---------------------------------------------------+
> + *  |RSC                                                |
> + *  | ctrl                                              |
> + *  |                                                   |
> + *  | Drvs:                                             |
> + *  | +-----------------------------------------------+ |
> + *  | |DRV0                                           | |
> + *  | | ctrl/config                                   | |
> + *  | | IRQ                                           | |
> + *  | |                                               | |
> + *  | | TCSes:                                        | |
> + *  | | +------------------------------------------+  | |
> + *  | | |TCS0  |  |  |  |  |  |  |  |  |  |  |  |  |  | |
> + *  | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15|  | |
> + *  | | |      |  |  |  |  |  |  |  |  |  |  |  |  |  | |
> + *  | | +------------------------------------------+  | |
> + *  | | +------------------------------------------+  | |
> + *  | | |TCS1  |  |  |  |  |  |  |  |  |  |  |  |  |  | |
> + *  | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15|  | |
> + *  | | |      |  |  |  |  |  |  |  |  |  |  |  |  |  | |
> + *  | | +------------------------------------------+  | |
> + *  | | +------------------------------------------+  | |
> + *  | | |TCS2  |  |  |  |  |  |  |  |  |  |  |  |  |  | |
> + *  | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15|  | |
> + *  | | |      |  |  |  |  |  |  |  |  |  |  |  |  |  | |
> + *  | | +------------------------------------------+  | |
> + *  | |                    ......                     | |
> + *  | +-----------------------------------------------+ |
> + *  | +-----------------------------------------------+ |
> + *  | |DRV1                                           | |
> + *  | | (same as DRV0)                                | |
> + *  | +-----------------------------------------------+ |
> + *  |                      ......                       |
> + *  +---------------------------------------------------+
> + */
> +
> +static u32 rpmh_rsc_reg_offset_ver_2_7[] = {
> +       [RSC_DRV_TCS_OFFSET]            = 672,
> +       [RSC_DRV_CMD_OFFSET]            = 20,
> +       [DRV_SOLVER_CONFIG]             = 0x04,
> +       [DRV_PRNT_CHLD_CONFIG]          = 0x0C,
> +       [RSC_DRV_IRQ_ENABLE]            = 0x00,
> +       [RSC_DRV_IRQ_STATUS]            = 0x04,
> +       [RSC_DRV_IRQ_CLEAR]             = 0x08,
> +       [RSC_DRV_CMD_WAIT_FOR_CMPL]     = 0x10,
> +       [RSC_DRV_CONTROL]               = 0x14,
> +       [RSC_DRV_STATUS]                = 0x18,
> +       [RSC_DRV_CMD_ENABLE]            = 0x1C,
> +       [RSC_DRV_CMD_MSGID]             = 0x30,
> +       [RSC_DRV_CMD_ADDR]              = 0x34,
> +       [RSC_DRV_CMD_DATA]              = 0x38,
> +       [RSC_DRV_CMD_STATUS]            = 0x3C,
> +       [RSC_DRV_CMD_RESP_DATA]         = 0x40,
> +};
> +
> +static inline void __iomem *
> +tcs_reg_addr(const struct rsc_drv *drv, int reg, int tcs_id)
> +{
> +       return drv->tcs_base + drv->regs[RSC_DRV_TCS_OFFSET] * tcs_id + reg;
> +}
> +
> +static inline void __iomem *
> +tcs_cmd_addr(const struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
> +{
> +       return tcs_reg_addr(drv, reg, tcs_id) + drv->regs[RSC_DRV_CMD_OFFSET] * cmd_id;
> +}
> +
> +static u32 read_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id)
> +{
> +       return readl_relaxed(tcs_reg_addr(drv, reg, tcs_id));
> +}
> +
> +static void write_tcs_cmd(const struct rsc_drv *drv, int reg, int tcs_id,
> +                         int cmd_id, u32 data)
> +{
> +       void __iomem *addr = tcs_cmd_addr(drv, reg, tcs_id, cmd_id);
> +
> +       debug("%s: tcs(m): %d cmd(n): %d addr: %#x data: %#x\n", drv->name,
> +             tcs_id, cmd_id, reg, data);
> +       writel_relaxed(data, addr);
> +}
> +
> +static void write_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id,
> +                         u32 data)
> +{
> +       void __iomem *addr = tcs_reg_addr(drv, reg, tcs_id);
> +
> +       debug("%s: tcs(m): %d addr: %#x data: %#x\n", drv->name,
> +             tcs_id, reg, data);
> +       writel_relaxed(data, addr);
> +}
> +
> +static void write_tcs_reg_sync(const struct rsc_drv *drv, int reg, int tcs_id,
> +                              u32 data)
> +{
> +       int i;
> +       void __iomem *addr = tcs_reg_addr(drv, reg, tcs_id);
> +
> +       debug("%s: tcs(m): %d addr: %#x data: %#x\n", drv->name,
> +             tcs_id, reg, data);
> +
> +       writel(data, addr);
> +
> +       /*
> +        * Wait until we read back the same value.  Use a counter rather than
> +        * ktime for timeout since this may be called after timekeeping stops.
> +        */
> +       for (i = 0; i < USEC_PER_SEC; i++) {
> +               if (readl(addr) == data)
> +                       return;
> +               udelay(1);
> +       }

Can we rather use readx_poll_sleep_timeout() here instead?

> +       pr_err("%s: error writing %#x to %d:%#x\n", drv->name,
> +              data, tcs_id, reg);
> +}
> +
> +/**
> + * tcs_invalidate() - Invalidate all TCSes of the given type (sleep or wake).
> + * @drv:  The RSC controller.
> + * @type: SLEEP_TCS or WAKE_TCS
> + *
> + * This will clear the "slots" variable of the given tcs_group and also
> + * tell the hardware to forget about all entries.
> + *
> + * The caller must ensure that no other RPMH actions are happening when this
> + * function is called, since otherwise the device may immediately become
> + * used again even before this function exits.
> + */
> +static void tcs_invalidate(struct rsc_drv *drv, int type)
> +{
> +       int m;
> +       struct tcs_group *tcs = &drv->tcs[type];
> +
> +       /* Caller ensures nobody else is running so no lock */
> +       if (bitmap_empty(tcs->slots, MAX_TCS_SLOTS))
> +               return;
> +
> +       for (m = tcs->offset; m < tcs->offset + tcs->num_tcs; m++)
> +               write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_ENABLE], m, 0);
> +
> +       bitmap_zero(tcs->slots, MAX_TCS_SLOTS);
> +}
> +
> +/**
> + * rpmh_rsc_invalidate() - Invalidate sleep and wake TCSes.
> + * @drv: The RSC controller.
> + *
> + * The caller must ensure that no other RPMH actions are happening when this
> + * function is called, since otherwise the device may immediately become
> + * used again even before this function exits.
> + */
> +void rpmh_rsc_invalidate(struct rsc_drv *drv)
> +{
> +       tcs_invalidate(drv, SLEEP_TCS);
> +       tcs_invalidate(drv, WAKE_TCS);
> +}
> +
> +/**
> + * rpmh_rsc_wait_for_resp() - Spin until we get a response from the rpmh
> + * @drv:    The controller.
> + * @tcs_id: The global ID of this TCS.
> + *
> + * This is for ACTIVE_ONLY transfers (which are the only ones we support in
> + * u-boot). As we don't support interrupts, we just spin on the IRQ_STATUS
> + * register until the bit is set to confirm that the TCS TX is done.
> + */
> +int rpmh_rsc_wait_for_resp(struct rsc_drv *drv, int tcs_id)
> +{
> +       u32 reg;
> +       int i;
> +
> +       reg = drv->regs[RSC_DRV_IRQ_STATUS];
> +
> +       debug("%s: waiting for response on tcs %d\n", __func__, tcs_id);
> +
> +       for (i = 0; i < 5 * USEC_PER_SEC; i++) {
> +               if (readl(tcs_reg_addr(drv, reg, tcs_id)) & BIT(tcs_id))
> +                       break;
> +               udelay(1);
> +       }

Can we rather use readx_poll_sleep_timeout() here instead?

> +
> +       if (i == 5 * USEC_PER_SEC) {
> +               printf("%s: timeout waiting for response\n", drv->name);
> +               return -ETIMEDOUT;
> +       }
> +
> +       writel_relaxed(BIT(tcs_id), drv->tcs_base + drv->regs[RSC_DRV_IRQ_CLEAR]);
> +
> +       return 0;
> +}
> +
> +/**
> + * __tcs_buffer_write() - Write to TCS hardware from a request; don't trigger.
> + * @drv:    The controller.
> + * @tcs_id: The global ID of this TCS.
> + * @cmd_id: The index within the TCS to start writing.
> + * @msg:    The message we want to send, which will contain several addr/data
> + *          pairs to program (but few enough that they all fit in one TCS).
> + *
> + * This is used for all types of transfers (active, sleep, and wake).
> + */
> +static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id,
> +                              const struct tcs_request *msg)
> +{
> +       u32 msgid;
> +       u32 cmd_msgid = CMD_MSGID_LEN | CMD_MSGID_WRITE;
> +       u32 cmd_enable = 0;
> +       struct tcs_cmd *cmd;
> +       int i, j;
> +
> +       /* u-boot: get a response to ensure everything is golden before continuing */
> +       cmd_msgid |= CMD_MSGID_RESP_REQ;
> +
> +       for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) {
> +               cmd = &msg->cmds[i];
> +               cmd_enable |= BIT(j);
> +               msgid = cmd_msgid;
> +               /*
> +                * Additionally, if the cmd->wait is set, make the command
> +                * response reqd even if the overall request was fire-n-forget.
> +                */
> +               msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0;
> +
> +               write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_MSGID], tcs_id, j, msgid);
> +               write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], tcs_id, j, cmd->addr);
> +               write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_DATA], tcs_id, j, cmd->data);
> +               debug("%s: tcs(m): %d [%s] cmd(n): %d msgid: %#x addr: %#x data: %#x complete: %d\n",
> +                     drv->name, tcs_id, msg->state == RPMH_ACTIVE_ONLY_STATE ? "active" : "?", j, msgid,
> +                     cmd->addr, cmd->data, cmd->wait);
> +       }
> +
> +       cmd_enable |= read_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id);
> +       write_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, cmd_enable);
> +}
> +
> +/**
> + * __tcs_set_trigger() - Start xfer on a TCS or unset trigger on a borrowed TCS
> + * @drv:     The controller.
> + * @tcs_id:  The global ID of this TCS.
> + * @trigger: If true then untrigger/retrigger. If false then just untrigger.
> + *
> + * In the normal case we only ever call with "trigger=true" to start a
> + * transfer. That will un-trigger/disable the TCS from the last transfer
> + * then trigger/enable for this transfer.
> + *
> + * If we borrowed a wake TCS for an active-only transfer we'll also call
> + * this function with "trigger=false" to just do the un-trigger/disable
> + * before using the TCS for wake purposes again.
> + *
> + * Note that the AP is only in charge of triggering active-only transfers.
> + * The AP never triggers sleep/wake values using this function.
> + */
> +static void __tcs_set_trigger(struct rsc_drv *drv, int tcs_id, bool trigger)
> +{
> +       u32 enable;
> +       u32 reg = drv->regs[RSC_DRV_CONTROL];
> +
> +       /*
> +        * 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, reg, tcs_id);
> +       enable &= ~TCS_AMC_MODE_TRIGGER;
> +       write_tcs_reg_sync(drv, reg, tcs_id, enable);
> +       enable &= ~TCS_AMC_MODE_ENABLE;
> +       write_tcs_reg_sync(drv, reg, tcs_id, enable);
> +
> +       if (trigger) {
> +               /* Enable the AMC mode on the TCS and then trigger the TCS */
> +               enable = TCS_AMC_MODE_ENABLE;
> +               write_tcs_reg_sync(drv, reg, tcs_id, enable);
> +               enable |= TCS_AMC_MODE_TRIGGER;
> +               write_tcs_reg(drv, reg, tcs_id, enable);
> +       }
> +}
> +
> +/**
> + * get_tcs_for_msg() - Get the tcs_group used to send the given message.
> + * @drv: The RSC controller.
> + * @msg: The message we want to send.
> + *
> + * This is normally pretty straightforward except if we are trying to send
> + * an ACTIVE_ONLY message but don't have any active_only TCSes.
> + *
> + * Return: A pointer to a tcs_group or an ERR_PTR.
> + */
> +static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv,
> +                                        const struct tcs_request *msg)
> +{
> +       if (msg->state != RPMH_ACTIVE_ONLY_STATE) {
> +               printf("WARN: only ACTIVE_ONLY state supported\n");
> +               return ERR_PTR(-EINVAL);
> +       }
> +
> +       return &drv->tcs[ACTIVE_TCS];
> +}
> +
> +/**
> + * rpmh_rsc_send_data() - Write / trigger active-only message.
> + * @drv: The controller.
> + * @msg: The data to be sent.
> + *
> + * NOTES:
> + * - This is only used for "ACTIVE_ONLY" since the limitations of this
> + *   function don't make sense for sleep/wake cases.
> + * - To do the transfer, we will grab a whole TCS for ourselves--we don't
> + *   try to share. If there are none available we'll wait indefinitely
> + *   for a free one.
> + * - This function will not wait for the commands to be finished, only for
> + *   data to be programmed into the RPMh. See rpmh_tx_done() which will
> + *   be called when the transfer is fully complete.
> + * - This function must be called with interrupts enabled. If the hardware
> + *   is busy doing someone else's transfer we need that transfer to fully
> + *   finish so that we can have the hardware, and to fully finish it needs
> + *   the interrupt handler to run. If the interrupts is set to run on the
> + *   active CPU this can never happen if interrupts are disabled.
> + *
> + * Return: 0 on success, -EINVAL on error.
> + */
> +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg)
> +{
> +       struct tcs_group *tcs;
> +       int tcs_id;
> +       unsigned long flags;
> +
> +       tcs = get_tcs_for_msg(drv, msg);
> +       if (IS_ERR(tcs))
> +               return PTR_ERR(tcs);
> +
> +       spin_lock_irqsave(&drv->lock, flags);

Locks aren't needed in U-Boot, can be dropped here and other places.

> +
> +       /* u-boot is single-threaded, always use the first TCS as we'll never conflict */
> +       tcs_id = tcs->offset;
> +
> +       tcs->req[tcs_id - tcs->offset] = msg;
> +       generic_set_bit(tcs_id, drv->tcs_in_use);
> +       if (msg->state == RPMH_ACTIVE_ONLY_STATE && tcs->type != ACTIVE_TCS) {
> +               /*
> +                * Clear previously programmed WAKE commands in selected
> +                * repurposed TCS to avoid triggering them. tcs->slots will be
> +                * cleaned from rpmh_flush() by invoking rpmh_rsc_invalidate()
> +                */
> +               write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, 0);
> +       }
> +       spin_unlock_irqrestore(&drv->lock, flags);
> +
> +       /*
> +        * These two can be done after the lock is released because:
> +        * - We marked "tcs_in_use" under lock.
> +        * - Once "tcs_in_use" has been marked nobody else could be writing
> +        *   to these registers until the interrupt goes off.
> +        * - The interrupt can't go off until we trigger w/ the last line
> +        *   of __tcs_set_trigger() below.
> +        */
> +       __tcs_buffer_write(drv, tcs_id, 0, msg);
> +       __tcs_set_trigger(drv, tcs_id, true);
> +
> +       rpmh_rsc_wait_for_resp(drv, tcs_id);
> +
> +       return 0;
> +}
> +
> +static int rpmh_probe_tcs_config(struct udevice *dev, struct rsc_drv *drv)
> +{
> +       struct tcs_type_config {
> +               u32 type;
> +               u32 n;
> +       } tcs_cfg[TCS_TYPE_NR] = { { 0 } };
> +       ofnode dn = dev_ofnode(dev);
> +       u32 config, max_tcs, ncpt, offset;
> +       int i, ret, n, st = 0;
> +       struct tcs_group *tcs;
> +
> +       ret = ofnode_read_u32(dn, "qcom,tcs-offset", &offset);
> +       if (ret)
> +               return ret;
> +       drv->tcs_base = drv->base + offset;
> +
> +       config = readl_relaxed(drv->base + drv->regs[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 = ofnode_read_u32_array(dn, "qcom,tcs-config", (u32 *)tcs_cfg, 2 * TCS_TYPE_NR);
> +       if (n < 0) {
> +               printf("RPMh: %s: error reading qcom,tcs-config %d\n", dev->name, n);
> +               return n;
> +       }
> +
> +       for (i = 0; i < TCS_TYPE_NR; i++) {
> +               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;
> +
> +               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 udevice *dev)
> +{
> +       ofnode dn = dev_ofnode(dev);
> +       ofnode rmem, node;
> +       struct rsc_drv *drv;
> +       char drv_id[10] = {0};
> +       int ret;
> +       u32 rsc_id;
> +
> +       /*
> +        * Even though RPMh doesn't directly use cmd-db, all of its children
> +        * do. We init cmd-db here or bail out if we can't. All child devices
> +        * can therefore safely assume that cmd-db is available.
> +        */
> +       rmem = ofnode_path("/reserved-memory");
> +       ofnode_for_each_subnode(node, rmem) {
> +               if (ofnode_device_is_compatible(node, "qcom,cmd-db"))
> +                       goto found;
> +       }
> +
> +       printf("Couldn't find qcom,cmd-db node!\n");
> +       return -ENODEV;
> +found:
> +       ret = cmd_db_init(node);
> +       if (ret < 0) {
> +               printf("Couldn't init cmd-db!\n");
> +               return ret;
> +       }
> +
> +       drv = dev_get_priv(dev);
> +
> +       ret = ofnode_read_u32(dn, "qcom,drv-id", &drv->id);
> +       if (ret)
> +               return ret;
> +
> +       drv->name = ofnode_get_property(dn, "label", NULL);
> +       if (!drv->name)
> +               drv->name = dev->name;
> +
> +       snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id);
> +       drv->base = (void __iomem*)dev_read_addr_name(dev, drv_id);
> +       if (IS_ERR(drv->base))
> +               return PTR_ERR(drv->base);
> +
> +       rsc_id = readl_relaxed(drv->base + RSC_DRV_ID);
> +       drv->ver.major = rsc_id & (MAJOR_VER_MASK << MAJOR_VER_SHIFT);
> +       drv->ver.major >>= MAJOR_VER_SHIFT;
> +       drv->ver.minor = rsc_id & (MINOR_VER_MASK << MINOR_VER_SHIFT);
> +       drv->ver.minor >>= MINOR_VER_SHIFT;
> +
> +       if (drv->ver.major == 3) {
> +               printf("RPMh v3 not supported\n");
> +               return -EOPNOTSUPP;
> +       } else {
> +               drv->regs = rpmh_rsc_reg_offset_ver_2_7;
> +       }
> +
> +       ret = rpmh_probe_tcs_config(dev, drv);
> +       if (ret)
> +               return ret;
> +
> +       spin_lock_init(&drv->lock);
> +       init_waitqueue_head(&drv->tcs_wait);

Similarly waitqueue should be dropped too.

-Sumit

> +       bitmap_zero(drv->tcs_in_use, MAX_TCS_NR);
> +
> +       /* Enable the active TCS to send requests immediately */
> +       writel_relaxed(drv->tcs[ACTIVE_TCS].mask,
> +                      drv->tcs_base + drv->regs[RSC_DRV_IRQ_ENABLE]);
> +
> +       spin_lock_init(&drv->client.cache_lock);
> +       INIT_LIST_HEAD(&drv->client.cache);
> +       INIT_LIST_HEAD(&drv->client.batch_cache);
> +
> +       dev_set_drvdata(dev, drv);
> +       drv->dev = dev;
> +
> +       log_debug("RPMh: %s: v%d.%d\n", dev->name, drv->ver.major, drv->ver.minor);
> +
> +       return ret;
> +}
> +
> +static const struct udevice_id qcom_rpmh_ids[] = {
> +       { .compatible = "qcom,rpmh-rsc" },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(qcom_rpmh_rsc) = {
> +       .name           = "qcom_rpmh_rsc",
> +       .id             = UCLASS_MISC,
> +       .priv_auto      = sizeof(struct rsc_drv),
> +       .probe          = rpmh_rsc_probe,
> +       .bind           = dm_scan_fdt_dev,
> +       .of_match       = qcom_rpmh_ids,
> +       /* rpmh is under CLUSTER_PD which we don't support */
> +       .flags          = DM_FLAG_DEFAULT_PD_CTRL_OFF,
> +};
> diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c
> new file mode 100644
> index 000000000000..f00c241de373
> --- /dev/null
> +++ b/drivers/soc/qcom/rpmh.c
> @@ -0,0 +1,110 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/bug.h>
> +#include <linux/kernel.h>
> +#include <linux/list.h>
> +#include <linux/types.h>
> +#include <dm/device.h>
> +
> +#include <soc/qcom/rpmh.h>
> +
> +#include "rpmh-internal.h"
> +
> +#define RPMH_TIMEOUT_MS                        msecs_to_jiffies(10000)
> +
> +#define DEFINE_RPMH_MSG_ONSTACK(device, s, name)       \
> +       struct rpmh_request name = {                    \
> +               .msg = {                                \
> +                       .state = s,                     \
> +                       .cmds = name.cmd,               \
> +                       .num_cmds = 0,                  \
> +               },                                      \
> +               .cmd = { { 0 } },                       \
> +               .dev = device,                          \
> +               .needs_free = false,                    \
> +       }
> +
> +#define ctrlr_to_drv(ctrlr) container_of(ctrlr, struct rsc_drv, client)
> +
> +static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct udevice *dev)
> +{
> +       struct rsc_drv *drv = (struct rsc_drv *)dev_get_priv(dev->parent);
> +
> +       if (!drv) {
> +               printf("BUG: no RPMh driver for %s (parent %s)\n", dev->name, dev->parent->name);
> +               BUG();
> +       }
> +
> +       return &drv->client;
> +}
> +
> +/**
> + * __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 udevice *dev, enum rpmh_state state,
> +                       struct rpmh_request *rpm_msg)
> +{
> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
> +
> +       debug("rpmh_write: %s, %d\n", dev->name, state);
> +
> +       if (state != RPMH_ACTIVE_ONLY_STATE) {
> +               printf("WARN: only ACTIVE_ONLY state supported\n");
> +               return -EINVAL;
> +       }
> +
> +       return rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg);
> +}
> +
> +static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state,
> +                          const struct tcs_cmd *cmd, u32 n)
> +{
> +       if (!cmd || !n || n > MAX_RPMH_PAYLOAD)
> +               return -EINVAL;
> +
> +       memcpy(req->cmd, cmd, n * sizeof(*cmd));
> +
> +       req->msg.state = state;
> +       req->msg.cmds = req->cmd;
> +       req->msg.num_cmds = n;
> +
> +       debug("rpmh_msg: %d, %d cmds [first %#x/%#x]\n", state, n, cmd->addr, cmd->data);
> +
> +       return 0;
> +}
> +
> +/**
> + * rpmh_write: Write a set of RPMH commands and block until response
> + *
> + * @dev: The device making the request
> + * @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 udevice *dev, enum rpmh_state state,
> +              const struct tcs_cmd *cmd, u32 n)
> +{
> +       DEFINE_RPMH_MSG_ONSTACK(dev, state, rpm_msg);
> +       int ret;
> +
> +       ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n);
> +       if (ret)
> +               return ret;
> +
> +       ret = __rpmh_write(dev, state, &rpm_msg);
> +
> +       return ret;
> +}
> diff --git a/include/soc/qcom/cmd-db.h b/include/soc/qcom/cmd-db.h
> new file mode 100644
> index 000000000000..1e22d86c66c9
> --- /dev/null
> +++ b/include/soc/qcom/cmd-db.h
> @@ -0,0 +1,42 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. */
> +
> +#ifndef __QCOM_COMMAND_DB_H__
> +#define __QCOM_COMMAND_DB_H__
> +
> +#include <linux/err.h>
> +#include <dm/device.h>
> +
> +enum cmd_db_hw_type {
> +       CMD_DB_HW_INVALID = 0,
> +       CMD_DB_HW_MIN     = 3,
> +       CMD_DB_HW_ARC     = CMD_DB_HW_MIN,
> +       CMD_DB_HW_VRM     = 4,
> +       CMD_DB_HW_BCM     = 5,
> +       CMD_DB_HW_MAX     = CMD_DB_HW_BCM,
> +       CMD_DB_HW_ALL     = 0xff,
> +};
> +
> +#if IS_ENABLED(CONFIG_QCOM_COMMAND_DB)
> +u32 cmd_db_read_addr(const char *resource_id);
> +
> +const void *cmd_db_read_aux_data(const char *resource_id, size_t *len);
> +
> +enum cmd_db_hw_type cmd_db_read_slave_id(const char *resource_id);
> +
> +int cmd_db_init(ofnode node);
> +
> +#else
> +static inline u32 cmd_db_read_addr(const char *resource_id)
> +{ return 0; }
> +
> +static inline const void *cmd_db_read_aux_data(const char *resource_id, size_t *len)
> +{ return ERR_PTR(-ENODEV); }
> +
> +static inline enum cmd_db_hw_type cmd_db_read_slave_id(const char *resource_id)
> +{ return -ENODEV; }
> +
> +static inline int cmd_db_ready(void)
> +{ return -ENODEV; }
> +#endif /* CONFIG_QCOM_COMMAND_DB */
> +#endif /* __QCOM_COMMAND_DB_H__ */
> diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h
> new file mode 100644
> index 000000000000..eb7c067cc5fd
> --- /dev/null
> +++ b/include/soc/qcom/rpmh.h
> @@ -0,0 +1,29 @@
> +/* 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 <linux/types.h>
> +#include <linux/errno.h>
> +#include <soc/qcom/tcs.h>
> +#include <dm/device-internal.h>
> +
> +#if IS_ENABLED(CONFIG_QCOM_RPMH)
> +int rpmh_write(const struct udevice *dev, enum rpmh_state state,
> +              const struct tcs_cmd *cmd, u32 n);
> +
> +#else
> +
> +static inline int rpmh_write(const struct udevice *dev, enum rpmh_state state,
> +                            const struct tcs_cmd *cmd, u32 n)
> +{ return -ENODEV; }
> +
> +#endif /* CONFIG_QCOM_RPMH */
> +
> +/* u-boot: no multithreading */
> +#define rpmh_write_async(dev, state, cmd, n) rpmh_write(dev, state, cmd, n)
> +
> +#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..c8d28b052f1d
> --- /dev/null
> +++ b/include/soc/qcom/tcs.h
> @@ -0,0 +1,78 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2016-2019, 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: ensure that this command is complete before returning.
> + *        Setting "wait" here only makes sense during rpmh_write_batch() for
> + *        active-only transfers, this is because:
> + *        rpmh_write() - Always waits.
> + *                       (DEFINE_RPMH_MSG_ONSTACK will set .wait_for_compl)
> + *        rpmh_write_async() - Never waits.
> + *                       (There's no request completion callback)
> + */
> +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.
> + * @num_cmds:       the number of @cmds in this request
> + * @cmds:           an array of tcs_cmds
> + */
> +struct tcs_request {
> +       enum rpmh_state state;
> +       u32 num_cmds;
> +       struct tcs_cmd *cmds;
> +};
> +
> +#define BCM_TCS_CMD_COMMIT_SHFT                30
> +#define BCM_TCS_CMD_COMMIT_MASK                0x40000000
> +#define BCM_TCS_CMD_VALID_SHFT         29
> +#define BCM_TCS_CMD_VALID_MASK         0x20000000
> +#define BCM_TCS_CMD_VOTE_X_SHFT                14
> +#define BCM_TCS_CMD_VOTE_MASK          0x3fff
> +#define BCM_TCS_CMD_VOTE_Y_SHFT                0
> +#define BCM_TCS_CMD_VOTE_Y_MASK                0xfffc000
> +
> +/* Construct a Bus Clock Manager (BCM) specific TCS command */
> +#define BCM_TCS_CMD(commit, valid, vote_x, vote_y)             \
> +       (((commit) << BCM_TCS_CMD_COMMIT_SHFT) |                \
> +       ((valid) << BCM_TCS_CMD_VALID_SHFT) |                   \
> +       ((cpu_to_le32(vote_x) &                                 \
> +       BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_X_SHFT) |    \
> +       ((cpu_to_le32(vote_y) &                                 \
> +       BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_Y_SHFT))
> +
> +#endif /* __SOC_QCOM_TCS_H__ */
>
> --
> 2.45.0
>
diff mbox series

Patch

diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index 03433bc0e6d2..8ceca0cb1386 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -39,8 +39,9 @@  config SOC_XILINX_VERSAL_NET
 	  Enable this option to select SoC device id driver for Xilinx Versal NET.
 	  This allows other drivers to verify the SoC familiy & revision using
 	  matching SoC attributes.
 
+source "drivers/soc/qcom/Kconfig"
 source "drivers/soc/samsung/Kconfig"
 source "drivers/soc/ti/Kconfig"
 
 endmenu
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index 610bf816d40a..c3d484e5bf6c 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -3,8 +3,9 @@ 
 # Makefile for the U-Boot SOC specific device drivers.
 
 obj-$(CONFIG_SOC_SAMSUNG) += samsung/
 obj-$(CONFIG_SOC_TI) += ti/
+obj-$(CONFIG_SOC_QCOM) += qcom/
 obj-$(CONFIG_SOC_DEVICE) += soc-uclass.o
 obj-$(CONFIG_SOC_DEVICE_TI_K3) += soc_ti_k3.o
 obj-$(CONFIG_SANDBOX) += soc_sandbox.o
 obj-$(CONFIG_SOC_XILINX_ZYNQMP) += soc_xilinx_zynqmp.o
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
new file mode 100644
index 000000000000..a0872c5b3c83
--- /dev/null
+++ b/drivers/soc/qcom/Kconfig
@@ -0,0 +1,25 @@ 
+# SPDX-License-Identifier: GPL-2.0+
+
+menuconfig SOC_QCOM
+	bool "Qualcomm SOC drivers support"
+	help
+	  Say Y here if you want to enable Qualcomm SOC drivers support.
+
+if SOC_QCOM
+
+config QCOM_COMMAND_DB
+	bool "Qualcomm Command DB"
+	help
+	  Command DB queries shared memory by key string for shared system
+	  resources. Platform drivers that require to set state of a shared
+	  resource on a RPM-hardened platform must use this database to get
+	  SoC specific identifier and information for the shared resources.
+
+config QCOM_RPMH
+	bool "Qualcomm RPMh support"
+	depends on QCOM_COMMAND_DB
+	help
+	  Say y here to support the Qualcomm RPMh (resource peripheral manager)
+	  if you need to control regulators on Qualcomm platforms, say y here.
+
+endif # SOC_QCOM
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
new file mode 100644
index 000000000000..4fca569cfb77
--- /dev/null
+++ b/drivers/soc/qcom/Makefile
@@ -0,0 +1,4 @@ 
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
+obj-$(CONFIG_QCOM_RPMH)	+= rpmh-rsc.o rpmh.o
diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c
new file mode 100644
index 000000000000..7bfd72ae2f3f
--- /dev/null
+++ b/drivers/soc/qcom/cmd-db.c
@@ -0,0 +1,246 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2023, Linaro Ltd.
+ */
+
+#include <dm/device.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/ioport.h>
+#include <linux/byteorder/generic.h>
+
+#include <soc/qcom/cmd-db.h>
+
+#define NUM_PRIORITY		2
+#define MAX_SLV_ID		8
+#define SLAVE_ID_MASK		0x7
+#define SLAVE_ID_SHIFT		16
+
+/**
+ * struct entry_header: header for each entry in cmddb
+ *
+ * @id: resource's identifier
+ * @priority: unused
+ * @addr: the address of the resource
+ * @len: length of the data
+ * @offset: offset from :@data_offset, start of the data
+ */
+struct entry_header {
+	u8 id[8];
+	__le32 priority[NUM_PRIORITY];
+	__le32 addr;
+	__le16 len;
+	__le16 offset;
+};
+
+/**
+ * struct rsc_hdr: resource header information
+ *
+ * @slv_id: id for the resource
+ * @header_offset: entry's header at offset from the end of the cmd_db_header
+ * @data_offset: entry's data at offset from the end of the cmd_db_header
+ * @cnt: number of entries for HW type
+ * @version: MSB is major, LSB is minor
+ * @reserved: reserved for future use.
+ */
+struct rsc_hdr {
+	__le16 slv_id;
+	__le16 header_offset;
+	__le16 data_offset;
+	__le16 cnt;
+	__le16 version;
+	__le16 reserved[3];
+};
+
+/**
+ * struct cmd_db_header: The DB header information
+ *
+ * @version: The cmd db version
+ * @magic: constant expected in the database
+ * @header: array of resources
+ * @checksum: checksum for the header. Unused.
+ * @reserved: reserved memory
+ * @data: driver specific data
+ */
+struct cmd_db_header {
+	__le32 version;
+	u8 magic[4];
+	struct rsc_hdr header[MAX_SLV_ID];
+	__le32 checksum;
+	__le32 reserved;
+	u8 data[];
+};
+
+/**
+ * DOC: Description of the Command DB database.
+ *
+ * At the start of the command DB memory is the cmd_db_header structure.
+ * The cmd_db_header holds the version, checksum, magic key as well as an
+ * array for header for each slave (depicted by the rsc_header). Each h/w
+ * based accelerator is a 'slave' (shared resource) and has slave id indicating
+ * the type of accelerator. The rsc_header is the header for such individual
+ * slaves of a given type. The entries for each of these slaves begin at the
+ * rsc_hdr.header_offset. In addition each slave could have auxiliary data
+ * that may be needed by the driver. The data for the slave starts at the
+ * entry_header.offset to the location pointed to by the rsc_hdr.data_offset.
+ *
+ * Drivers have a stringified key to a slave/resource. They can query the slave
+ * information and get the slave id and the auxiliary data and the length of the
+ * data. Using this information, they can format the request to be sent to the
+ * h/w accelerator and request a resource state.
+ */
+
+static const u8 CMD_DB_MAGIC[] = { 0xdb, 0x30, 0x03, 0x0c };
+
+static bool cmd_db_magic_matches(const struct cmd_db_header *header)
+{
+	const u8 *magic = header->magic;
+
+	return memcmp(magic, CMD_DB_MAGIC, ARRAY_SIZE(CMD_DB_MAGIC)) == 0;
+}
+
+static struct cmd_db_header *cmd_db_header;
+
+static inline const void *rsc_to_entry_header(const struct rsc_hdr *hdr)
+{
+	u16 offset = le16_to_cpu(hdr->header_offset);
+
+	return cmd_db_header->data + offset;
+}
+
+static inline void *
+rsc_offset(const struct rsc_hdr *hdr, const struct entry_header *ent)
+{
+	u16 offset = le16_to_cpu(hdr->data_offset);
+	u16 loffset = le16_to_cpu(ent->offset);
+
+	return cmd_db_header->data + offset + loffset;
+}
+
+static int cmd_db_get_header(const char *id, const struct entry_header **eh,
+			     const struct rsc_hdr **rh)
+{
+	const struct rsc_hdr *rsc_hdr;
+	const struct entry_header *ent;
+	int i, j;
+	u8 query[sizeof(ent->id)] __nonstring;
+
+	/*
+	 * Pad out query string to same length as in DB. NOTE: the output
+	 * query string is not necessarily '\0' terminated if it bumps up
+	 * against the max size. That's OK and expected.
+	 */
+	strncpy(query, id, sizeof(query));
+
+	for (i = 0; i < MAX_SLV_ID; i++) {
+		rsc_hdr = &cmd_db_header->header[i];
+		if (!rsc_hdr->slv_id)
+			break;
+
+		ent = rsc_to_entry_header(rsc_hdr);
+		for (j = 0; j < le16_to_cpu(rsc_hdr->cnt); j++, ent++) {
+			if (memcmp(ent->id, query, sizeof(ent->id)) == 0) {
+				if (eh)
+					*eh = ent;
+				if (rh)
+					*rh = rsc_hdr;
+				return 0;
+			}
+		}
+	}
+
+	return -ENODEV;
+}
+
+/**
+ * cmd_db_read_addr() - Query command db for resource id address.
+ *
+ * @id: resource id to query for address
+ *
+ * Return: resource address on success, 0 on error
+ *
+ * This is used to retrieve resource address based on resource
+ * id.
+ */
+u32 cmd_db_read_addr(const char *id)
+{
+	int ret;
+	const struct entry_header *ent;
+
+	ret = cmd_db_get_header(id, &ent, NULL);
+
+	return ret < 0 ? 0 : le32_to_cpu(ent->addr);
+}
+EXPORT_SYMBOL(cmd_db_read_addr);
+
+/**
+ * cmd_db_read_aux_data() - Query command db for aux data.
+ *
+ *  @id: Resource to retrieve AUX Data on
+ *  @len: size of data buffer returned
+ *
+ *  Return: pointer to data on success, error pointer otherwise
+ */
+const void *cmd_db_read_aux_data(const char *id, size_t *len)
+{
+	int ret;
+	const struct entry_header *ent;
+	const struct rsc_hdr *rsc_hdr;
+
+	ret = cmd_db_get_header(id, &ent, &rsc_hdr);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (len)
+		*len = le16_to_cpu(ent->len);
+
+	return rsc_offset(rsc_hdr, ent);
+}
+EXPORT_SYMBOL(cmd_db_read_aux_data);
+
+/**
+ * cmd_db_read_slave_id - Get the slave ID for a given resource address
+ *
+ * @id: Resource id to query the DB for version
+ *
+ * Return: cmd_db_hw_type enum on success, CMD_DB_HW_INVALID on error
+ */
+enum cmd_db_hw_type cmd_db_read_slave_id(const char *id)
+{
+	int ret;
+	const struct entry_header *ent;
+	u32 addr;
+
+	ret = cmd_db_get_header(id, &ent, NULL);
+	if (ret < 0)
+		return CMD_DB_HW_INVALID;
+
+	addr = le32_to_cpu(ent->addr);
+	return (addr >> SLAVE_ID_SHIFT) & SLAVE_ID_MASK;
+}
+EXPORT_SYMBOL(cmd_db_read_slave_id);
+
+int cmd_db_init(ofnode node)
+{
+	void __iomem *base;
+
+	debug("%s(%s)\n", __func__, ofnode_get_name(node));
+
+	base = (void __iomem *)ofnode_get_addr(node);
+	if ((fdt_addr_t)base == FDT_ADDR_T_NONE) {
+		log_err("%s: Failed to read base address\n", __func__);
+		return -ENOENT;
+	}
+
+	cmd_db_header = base;
+	if (!cmd_db_magic_matches(cmd_db_header)) {
+		log_err("%s: Invalid Command DB Magic\n", __func__);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(cmd_db_init);
diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h
new file mode 100644
index 000000000000..9dbb1c51cc35
--- /dev/null
+++ b/drivers/soc/qcom/rpmh-internal.h
@@ -0,0 +1,141 @@ 
+// 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)
+
+#define USEC_PER_SEC           1000000UL
+
+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.
+ * @req:       Requests that are sent from the TCS; only used for ACTIVE_ONLY
+ *             transfers (could be on a wake/sleep TCS if we are borrowing for
+ *             an ACTIVE_ONLY transfer).
+ *             Start: grab drv->lock, set req, set tcs_in_use, drop drv->lock,
+ *                    trigger
+ *             End: get irq, access req,
+ *                  grab drv->lock, clear tcs_in_use, drop drv->lock
+ * @slots:     Indicates which of @cmd_addr are occupied; only used for
+ *             SLEEP / WAKE TCSs.  Things are tightly packed in the
+ *             case that (ncpt < MAX_CMDS_PER_TCS).  That is if ncpt = 2 and
+ *             MAX_CMDS_PER_TCS = 16 then bit[2] = the first bit in 2nd TCS.
+ */
+struct tcs_group {
+	struct rsc_drv *drv;
+	int type;
+	u32 mask;
+	u32 offset;
+	int num_tcs;
+	int ncpt;
+	const struct tcs_request *req[MAX_TCS_PER_TYPE];
+	DECLARE_BITMAP(slots, MAX_TCS_SLOTS);
+};
+
+/**
+ * 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
+ * @needs_free: check to free dynamically allocated request object
+ */
+struct rpmh_request {
+	struct tcs_request msg;
+	struct tcs_cmd cmd[MAX_RPMH_PAYLOAD];
+	const struct udevice *dev;
+	bool needs_free;
+};
+
+/**
+ * struct rpmh_ctrlr: our representation of the controller
+ *
+ * @cache: the list of cached requests
+ * @cache_lock: synchronize access to the cache data
+ * @dirty: was the cache updated since flush
+ * @batch_cache: Cache sleep and wake requests sent as batch
+ */
+struct rpmh_ctrlr {
+	struct list_head cache;
+	bool dirty;
+	struct list_head batch_cache;
+};
+
+struct rsc_ver {
+	u32 major;
+	u32 minor;
+};
+
+/**
+ * struct rsc_drv: the Direct Resource Voter (DRV) of the
+ * Resource State Coordinator controller (RSC)
+ *
+ * @name:               Controller identifier.
+ * @base:               Start address of the DRV registers in this controller.
+ * @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.
+ * @rsc_pm:             CPU PM notifier for controller.
+ *                      Used when solver mode is not present.
+ * @cpus_in_pm:         Number of CPUs not in idle power collapse.
+ *                      Used when solver mode and "power-domains" is not present.
+ * @genpd_nb:           PM Domain notifier for cluster genpd notifications.
+ * @tcs:                TCS groups.
+ * @tcs_in_use:         S/W state of the TCS; only set for ACTIVE_ONLY
+ *                      transfers, but might show a sleep/wake TCS in use if
+ *                      it was borrowed for an active_only transfer.  You
+ *                      must hold the lock in this struct (AKA drv->lock) in
+ *                      order to update this.
+ * @lock:               Synchronize state of the controller.  If RPMH's cache
+ *                      lock will also be held, the order is: drv->lock then
+ *                      cache_lock.
+ * @tcs_wait:           Wait queue used to wait for @tcs_in_use to free up a
+ *                      slot
+ * @client:             Handle to the DRV's client.
+ * @dev:                RSC device.
+ */
+struct rsc_drv {
+	const char *name;
+	void __iomem *base;
+	void __iomem *tcs_base;
+	int id;
+	int num_tcs;
+	struct tcs_group tcs[TCS_TYPE_NR];
+	DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR);
+	struct rpmh_ctrlr client;
+	struct udevice *dev;
+	struct rsc_ver ver;
+	u32 *regs;
+};
+
+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);
+void rpmh_rsc_invalidate(struct rsc_drv *drv);
+void rpmh_rsc_write_next_wakeup(struct rsc_drv *drv);
+
+int rpmh_rsc_wait_for_resp(struct rsc_drv *drv, int tcs_id);
+int rpmh_flush(struct rpmh_ctrlr *ctrlr);
+
+#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..3c73dc0ffe25
--- /dev/null
+++ b/drivers/soc/qcom/rpmh-rsc.c
@@ -0,0 +1,619 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2023, Linaro Ltd.
+ */
+
+#include <linux/err.h>
+#include <linux/types.h>
+#include <dm.h>
+#include <dm/ofnode.h>
+#include <dm/devres.h>
+#include <dm/device_compat.h>
+#include <linux/delay.h>
+#include <dm/lists.h>
+#include <errno.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+#include <linux/bitmap.h>
+#include <log.h>
+
+#include <dt-bindings/soc/qcom,rpmh-rsc.h>
+#include "rpmh-internal.h"
+
+#include <soc/qcom/rpmh.h>
+#include <soc/qcom/cmd-db.h>
+
+#define RSC_DRV_ID			0
+
+#define MAJOR_VER_MASK			0xFF
+#define MAJOR_VER_SHIFT			16
+#define MINOR_VER_MASK			0xFF
+#define MINOR_VER_SHIFT			8
+
+enum {
+	RSC_DRV_TCS_OFFSET,
+	RSC_DRV_CMD_OFFSET,
+	DRV_SOLVER_CONFIG,
+	DRV_PRNT_CHLD_CONFIG,
+	RSC_DRV_IRQ_ENABLE,
+	RSC_DRV_IRQ_STATUS,
+	RSC_DRV_IRQ_CLEAR,
+	RSC_DRV_CMD_WAIT_FOR_CMPL,
+	RSC_DRV_CONTROL,
+	RSC_DRV_STATUS,
+	RSC_DRV_CMD_ENABLE,
+	RSC_DRV_CMD_MSGID,
+	RSC_DRV_CMD_ADDR,
+	RSC_DRV_CMD_DATA,
+	RSC_DRV_CMD_STATUS,
+	RSC_DRV_CMD_RESP_DATA,
+};
+
+/* DRV HW Solver Configuration Information Register */
+#define DRV_HW_SOLVER_MASK		1
+#define DRV_HW_SOLVER_SHIFT		24
+
+/* DRV TCS Configuration Information Register */
+#define DRV_NUM_TCS_MASK		0x3F
+#define DRV_NUM_TCS_SHIFT		6
+#define DRV_NCPT_MASK			0x1F
+#define DRV_NCPT_SHIFT			27
+
+/* Offsets for CONTROL TCS Registers */
+#define RSC_DRV_CTL_TCS_DATA_HI		0x38
+#define RSC_DRV_CTL_TCS_DATA_HI_MASK	0xFFFFFF
+#define RSC_DRV_CTL_TCS_DATA_HI_VALID	BIT(31)
+#define RSC_DRV_CTL_TCS_DATA_LO		0x40
+#define RSC_DRV_CTL_TCS_DATA_LO_MASK	0xFFFFFFFF
+#define RSC_DRV_CTL_TCS_DATA_SIZE	32
+
+#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)
+
+/*
+ * Here's a high level overview of how all the registers in RPMH work
+ * together:
+ *
+ * - The main rpmh-rsc address is the base of a register space that can
+ *   be used to find overall configuration of the hardware
+ *   (DRV_PRNT_CHLD_CONFIG). Also found within the rpmh-rsc register
+ *   space are all the TCS blocks. The offset of the TCS blocks is
+ *   specified in the device tree by "qcom,tcs-offset" and used to
+ *   compute tcs_base.
+ * - TCS blocks come one after another. Type, count, and order are
+ *   specified by the device tree as "qcom,tcs-config".
+ * - Each TCS block has some registers, then space for up to 16 commands.
+ *   Note that though address space is reserved for 16 commands, fewer
+ *   might be present. See ncpt (num cmds per TCS).
+ *
+ * Here's a picture:
+ *
+ *  +---------------------------------------------------+
+ *  |RSC                                                |
+ *  | ctrl                                              |
+ *  |                                                   |
+ *  | Drvs:                                             |
+ *  | +-----------------------------------------------+ |
+ *  | |DRV0                                           | |
+ *  | | ctrl/config                                   | |
+ *  | | IRQ                                           | |
+ *  | |                                               | |
+ *  | | TCSes:                                        | |
+ *  | | +------------------------------------------+  | |
+ *  | | |TCS0  |  |  |  |  |  |  |  |  |  |  |  |  |  | |
+ *  | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15|  | |
+ *  | | |      |  |  |  |  |  |  |  |  |  |  |  |  |  | |
+ *  | | +------------------------------------------+  | |
+ *  | | +------------------------------------------+  | |
+ *  | | |TCS1  |  |  |  |  |  |  |  |  |  |  |  |  |  | |
+ *  | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15|  | |
+ *  | | |      |  |  |  |  |  |  |  |  |  |  |  |  |  | |
+ *  | | +------------------------------------------+  | |
+ *  | | +------------------------------------------+  | |
+ *  | | |TCS2  |  |  |  |  |  |  |  |  |  |  |  |  |  | |
+ *  | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15|  | |
+ *  | | |      |  |  |  |  |  |  |  |  |  |  |  |  |  | |
+ *  | | +------------------------------------------+  | |
+ *  | |                    ......                     | |
+ *  | +-----------------------------------------------+ |
+ *  | +-----------------------------------------------+ |
+ *  | |DRV1                                           | |
+ *  | | (same as DRV0)                                | |
+ *  | +-----------------------------------------------+ |
+ *  |                      ......                       |
+ *  +---------------------------------------------------+
+ */
+
+static u32 rpmh_rsc_reg_offset_ver_2_7[] = {
+	[RSC_DRV_TCS_OFFSET]		= 672,
+	[RSC_DRV_CMD_OFFSET]		= 20,
+	[DRV_SOLVER_CONFIG]		= 0x04,
+	[DRV_PRNT_CHLD_CONFIG]		= 0x0C,
+	[RSC_DRV_IRQ_ENABLE]		= 0x00,
+	[RSC_DRV_IRQ_STATUS]		= 0x04,
+	[RSC_DRV_IRQ_CLEAR]		= 0x08,
+	[RSC_DRV_CMD_WAIT_FOR_CMPL]	= 0x10,
+	[RSC_DRV_CONTROL]		= 0x14,
+	[RSC_DRV_STATUS]		= 0x18,
+	[RSC_DRV_CMD_ENABLE]		= 0x1C,
+	[RSC_DRV_CMD_MSGID]		= 0x30,
+	[RSC_DRV_CMD_ADDR]		= 0x34,
+	[RSC_DRV_CMD_DATA]		= 0x38,
+	[RSC_DRV_CMD_STATUS]		= 0x3C,
+	[RSC_DRV_CMD_RESP_DATA]		= 0x40,
+};
+
+static inline void __iomem *
+tcs_reg_addr(const struct rsc_drv *drv, int reg, int tcs_id)
+{
+	return drv->tcs_base + drv->regs[RSC_DRV_TCS_OFFSET] * tcs_id + reg;
+}
+
+static inline void __iomem *
+tcs_cmd_addr(const struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
+{
+	return tcs_reg_addr(drv, reg, tcs_id) + drv->regs[RSC_DRV_CMD_OFFSET] * cmd_id;
+}
+
+static u32 read_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id)
+{
+	return readl_relaxed(tcs_reg_addr(drv, reg, tcs_id));
+}
+
+static void write_tcs_cmd(const struct rsc_drv *drv, int reg, int tcs_id,
+			  int cmd_id, u32 data)
+{
+	void __iomem *addr = tcs_cmd_addr(drv, reg, tcs_id, cmd_id);
+
+	debug("%s: tcs(m): %d cmd(n): %d addr: %#x data: %#x\n", drv->name,
+	      tcs_id, cmd_id, reg, data);
+	writel_relaxed(data, addr);
+}
+
+static void write_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id,
+			  u32 data)
+{
+	void __iomem *addr = tcs_reg_addr(drv, reg, tcs_id);
+
+	debug("%s: tcs(m): %d addr: %#x data: %#x\n", drv->name,
+	      tcs_id, reg, data);
+	writel_relaxed(data, addr);
+}
+
+static void write_tcs_reg_sync(const struct rsc_drv *drv, int reg, int tcs_id,
+			       u32 data)
+{
+	int i;
+	void __iomem *addr = tcs_reg_addr(drv, reg, tcs_id);
+
+	debug("%s: tcs(m): %d addr: %#x data: %#x\n", drv->name,
+	      tcs_id, reg, data);
+
+	writel(data, addr);
+
+	/*
+	 * Wait until we read back the same value.  Use a counter rather than
+	 * ktime for timeout since this may be called after timekeeping stops.
+	 */
+	for (i = 0; i < USEC_PER_SEC; i++) {
+		if (readl(addr) == data)
+			return;
+		udelay(1);
+	}
+	pr_err("%s: error writing %#x to %d:%#x\n", drv->name,
+	       data, tcs_id, reg);
+}
+
+/**
+ * tcs_invalidate() - Invalidate all TCSes of the given type (sleep or wake).
+ * @drv:  The RSC controller.
+ * @type: SLEEP_TCS or WAKE_TCS
+ *
+ * This will clear the "slots" variable of the given tcs_group and also
+ * tell the hardware to forget about all entries.
+ *
+ * The caller must ensure that no other RPMH actions are happening when this
+ * function is called, since otherwise the device may immediately become
+ * used again even before this function exits.
+ */
+static void tcs_invalidate(struct rsc_drv *drv, int type)
+{
+	int m;
+	struct tcs_group *tcs = &drv->tcs[type];
+
+	/* Caller ensures nobody else is running so no lock */
+	if (bitmap_empty(tcs->slots, MAX_TCS_SLOTS))
+		return;
+
+	for (m = tcs->offset; m < tcs->offset + tcs->num_tcs; m++)
+		write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_ENABLE], m, 0);
+
+	bitmap_zero(tcs->slots, MAX_TCS_SLOTS);
+}
+
+/**
+ * rpmh_rsc_invalidate() - Invalidate sleep and wake TCSes.
+ * @drv: The RSC controller.
+ *
+ * The caller must ensure that no other RPMH actions are happening when this
+ * function is called, since otherwise the device may immediately become
+ * used again even before this function exits.
+ */
+void rpmh_rsc_invalidate(struct rsc_drv *drv)
+{
+	tcs_invalidate(drv, SLEEP_TCS);
+	tcs_invalidate(drv, WAKE_TCS);
+}
+
+/**
+ * rpmh_rsc_wait_for_resp() - Spin until we get a response from the rpmh
+ * @drv:    The controller.
+ * @tcs_id: The global ID of this TCS.
+ *
+ * This is for ACTIVE_ONLY transfers (which are the only ones we support in
+ * u-boot). As we don't support interrupts, we just spin on the IRQ_STATUS
+ * register until the bit is set to confirm that the TCS TX is done.
+ */
+int rpmh_rsc_wait_for_resp(struct rsc_drv *drv, int tcs_id)
+{
+	u32 reg;
+	int i;
+
+	reg = drv->regs[RSC_DRV_IRQ_STATUS];
+
+	debug("%s: waiting for response on tcs %d\n", __func__, tcs_id);
+
+	for (i = 0; i < 5 * USEC_PER_SEC; i++) {
+		if (readl(tcs_reg_addr(drv, reg, tcs_id)) & BIT(tcs_id))
+			break;
+		udelay(1);
+	}
+
+	if (i == 5 * USEC_PER_SEC) {
+		printf("%s: timeout waiting for response\n", drv->name);
+		return -ETIMEDOUT;
+	}
+
+	writel_relaxed(BIT(tcs_id), drv->tcs_base + drv->regs[RSC_DRV_IRQ_CLEAR]);
+
+	return 0;
+}
+
+/**
+ * __tcs_buffer_write() - Write to TCS hardware from a request; don't trigger.
+ * @drv:    The controller.
+ * @tcs_id: The global ID of this TCS.
+ * @cmd_id: The index within the TCS to start writing.
+ * @msg:    The message we want to send, which will contain several addr/data
+ *          pairs to program (but few enough that they all fit in one TCS).
+ *
+ * This is used for all types of transfers (active, sleep, and wake).
+ */
+static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id,
+			       const struct tcs_request *msg)
+{
+	u32 msgid;
+	u32 cmd_msgid = CMD_MSGID_LEN | CMD_MSGID_WRITE;
+	u32 cmd_enable = 0;
+	struct tcs_cmd *cmd;
+	int i, j;
+
+	/* u-boot: get a response to ensure everything is golden before continuing */
+	cmd_msgid |= CMD_MSGID_RESP_REQ;
+
+	for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) {
+		cmd = &msg->cmds[i];
+		cmd_enable |= BIT(j);
+		msgid = cmd_msgid;
+		/*
+		 * Additionally, if the cmd->wait is set, make the command
+		 * response reqd even if the overall request was fire-n-forget.
+		 */
+		msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0;
+
+		write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_MSGID], tcs_id, j, msgid);
+		write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], tcs_id, j, cmd->addr);
+		write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_DATA], tcs_id, j, cmd->data);
+		debug("%s: tcs(m): %d [%s] cmd(n): %d msgid: %#x addr: %#x data: %#x complete: %d\n",
+		      drv->name, tcs_id, msg->state == RPMH_ACTIVE_ONLY_STATE ? "active" : "?", j, msgid,
+		      cmd->addr, cmd->data, cmd->wait);
+	}
+
+	cmd_enable |= read_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id);
+	write_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, cmd_enable);
+}
+
+/**
+ * __tcs_set_trigger() - Start xfer on a TCS or unset trigger on a borrowed TCS
+ * @drv:     The controller.
+ * @tcs_id:  The global ID of this TCS.
+ * @trigger: If true then untrigger/retrigger. If false then just untrigger.
+ *
+ * In the normal case we only ever call with "trigger=true" to start a
+ * transfer. That will un-trigger/disable the TCS from the last transfer
+ * then trigger/enable for this transfer.
+ *
+ * If we borrowed a wake TCS for an active-only transfer we'll also call
+ * this function with "trigger=false" to just do the un-trigger/disable
+ * before using the TCS for wake purposes again.
+ *
+ * Note that the AP is only in charge of triggering active-only transfers.
+ * The AP never triggers sleep/wake values using this function.
+ */
+static void __tcs_set_trigger(struct rsc_drv *drv, int tcs_id, bool trigger)
+{
+	u32 enable;
+	u32 reg = drv->regs[RSC_DRV_CONTROL];
+
+	/*
+	 * 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, reg, tcs_id);
+	enable &= ~TCS_AMC_MODE_TRIGGER;
+	write_tcs_reg_sync(drv, reg, tcs_id, enable);
+	enable &= ~TCS_AMC_MODE_ENABLE;
+	write_tcs_reg_sync(drv, reg, tcs_id, enable);
+
+	if (trigger) {
+		/* Enable the AMC mode on the TCS and then trigger the TCS */
+		enable = TCS_AMC_MODE_ENABLE;
+		write_tcs_reg_sync(drv, reg, tcs_id, enable);
+		enable |= TCS_AMC_MODE_TRIGGER;
+		write_tcs_reg(drv, reg, tcs_id, enable);
+	}
+}
+
+/**
+ * get_tcs_for_msg() - Get the tcs_group used to send the given message.
+ * @drv: The RSC controller.
+ * @msg: The message we want to send.
+ *
+ * This is normally pretty straightforward except if we are trying to send
+ * an ACTIVE_ONLY message but don't have any active_only TCSes.
+ *
+ * Return: A pointer to a tcs_group or an ERR_PTR.
+ */
+static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv,
+					 const struct tcs_request *msg)
+{
+	if (msg->state != RPMH_ACTIVE_ONLY_STATE) {
+		printf("WARN: only ACTIVE_ONLY state supported\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	return &drv->tcs[ACTIVE_TCS];
+}
+
+/**
+ * rpmh_rsc_send_data() - Write / trigger active-only message.
+ * @drv: The controller.
+ * @msg: The data to be sent.
+ *
+ * NOTES:
+ * - This is only used for "ACTIVE_ONLY" since the limitations of this
+ *   function don't make sense for sleep/wake cases.
+ * - To do the transfer, we will grab a whole TCS for ourselves--we don't
+ *   try to share. If there are none available we'll wait indefinitely
+ *   for a free one.
+ * - This function will not wait for the commands to be finished, only for
+ *   data to be programmed into the RPMh. See rpmh_tx_done() which will
+ *   be called when the transfer is fully complete.
+ * - This function must be called with interrupts enabled. If the hardware
+ *   is busy doing someone else's transfer we need that transfer to fully
+ *   finish so that we can have the hardware, and to fully finish it needs
+ *   the interrupt handler to run. If the interrupts is set to run on the
+ *   active CPU this can never happen if interrupts are disabled.
+ *
+ * Return: 0 on success, -EINVAL on error.
+ */
+int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg)
+{
+	struct tcs_group *tcs;
+	int tcs_id;
+	unsigned long flags;
+
+	tcs = get_tcs_for_msg(drv, msg);
+	if (IS_ERR(tcs))
+		return PTR_ERR(tcs);
+
+	spin_lock_irqsave(&drv->lock, flags);
+
+	/* u-boot is single-threaded, always use the first TCS as we'll never conflict */
+	tcs_id = tcs->offset;
+
+	tcs->req[tcs_id - tcs->offset] = msg;
+	generic_set_bit(tcs_id, drv->tcs_in_use);
+	if (msg->state == RPMH_ACTIVE_ONLY_STATE && tcs->type != ACTIVE_TCS) {
+		/*
+		 * Clear previously programmed WAKE commands in selected
+		 * repurposed TCS to avoid triggering them. tcs->slots will be
+		 * cleaned from rpmh_flush() by invoking rpmh_rsc_invalidate()
+		 */
+		write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, 0);
+	}
+	spin_unlock_irqrestore(&drv->lock, flags);
+
+	/*
+	 * These two can be done after the lock is released because:
+	 * - We marked "tcs_in_use" under lock.
+	 * - Once "tcs_in_use" has been marked nobody else could be writing
+	 *   to these registers until the interrupt goes off.
+	 * - The interrupt can't go off until we trigger w/ the last line
+	 *   of __tcs_set_trigger() below.
+	 */
+	__tcs_buffer_write(drv, tcs_id, 0, msg);
+	__tcs_set_trigger(drv, tcs_id, true);
+
+	rpmh_rsc_wait_for_resp(drv, tcs_id);
+
+	return 0;
+}
+
+static int rpmh_probe_tcs_config(struct udevice *dev, struct rsc_drv *drv)
+{
+	struct tcs_type_config {
+		u32 type;
+		u32 n;
+	} tcs_cfg[TCS_TYPE_NR] = { { 0 } };
+	ofnode dn = dev_ofnode(dev);
+	u32 config, max_tcs, ncpt, offset;
+	int i, ret, n, st = 0;
+	struct tcs_group *tcs;
+
+	ret = ofnode_read_u32(dn, "qcom,tcs-offset", &offset);
+	if (ret)
+		return ret;
+	drv->tcs_base = drv->base + offset;
+
+	config = readl_relaxed(drv->base + drv->regs[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 = ofnode_read_u32_array(dn, "qcom,tcs-config", (u32 *)tcs_cfg, 2 * TCS_TYPE_NR);
+	if (n < 0) {
+		printf("RPMh: %s: error reading qcom,tcs-config %d\n", dev->name, n);
+		return n;
+	}
+
+	for (i = 0; i < TCS_TYPE_NR; i++) {
+		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;
+
+		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 udevice *dev)
+{
+	ofnode dn = dev_ofnode(dev);
+	ofnode rmem, node;
+	struct rsc_drv *drv;
+	char drv_id[10] = {0};
+	int ret;
+	u32 rsc_id;
+
+	/*
+	 * Even though RPMh doesn't directly use cmd-db, all of its children
+	 * do. We init cmd-db here or bail out if we can't. All child devices
+	 * can therefore safely assume that cmd-db is available.
+	 */
+	rmem = ofnode_path("/reserved-memory");
+	ofnode_for_each_subnode(node, rmem) {
+		if (ofnode_device_is_compatible(node, "qcom,cmd-db"))
+			goto found;
+	}
+
+	printf("Couldn't find qcom,cmd-db node!\n");
+	return -ENODEV;
+found:
+	ret = cmd_db_init(node);
+	if (ret < 0) {
+		printf("Couldn't init cmd-db!\n");
+		return ret;
+	}
+
+	drv = dev_get_priv(dev);
+
+	ret = ofnode_read_u32(dn, "qcom,drv-id", &drv->id);
+	if (ret)
+		return ret;
+
+	drv->name = ofnode_get_property(dn, "label", NULL);
+	if (!drv->name)
+		drv->name = dev->name;
+
+	snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id);
+	drv->base = (void __iomem*)dev_read_addr_name(dev, drv_id);
+	if (IS_ERR(drv->base))
+		return PTR_ERR(drv->base);
+
+	rsc_id = readl_relaxed(drv->base + RSC_DRV_ID);
+	drv->ver.major = rsc_id & (MAJOR_VER_MASK << MAJOR_VER_SHIFT);
+	drv->ver.major >>= MAJOR_VER_SHIFT;
+	drv->ver.minor = rsc_id & (MINOR_VER_MASK << MINOR_VER_SHIFT);
+	drv->ver.minor >>= MINOR_VER_SHIFT;
+
+	if (drv->ver.major == 3) {
+		printf("RPMh v3 not supported\n");
+		return -EOPNOTSUPP;
+	} else {
+		drv->regs = rpmh_rsc_reg_offset_ver_2_7;
+	}
+
+	ret = rpmh_probe_tcs_config(dev, drv);
+	if (ret)
+		return ret;
+
+	spin_lock_init(&drv->lock);
+	init_waitqueue_head(&drv->tcs_wait);
+	bitmap_zero(drv->tcs_in_use, MAX_TCS_NR);
+
+	/* Enable the active TCS to send requests immediately */
+	writel_relaxed(drv->tcs[ACTIVE_TCS].mask,
+		       drv->tcs_base + drv->regs[RSC_DRV_IRQ_ENABLE]);
+
+	spin_lock_init(&drv->client.cache_lock);
+	INIT_LIST_HEAD(&drv->client.cache);
+	INIT_LIST_HEAD(&drv->client.batch_cache);
+
+	dev_set_drvdata(dev, drv);
+	drv->dev = dev;
+
+	log_debug("RPMh: %s: v%d.%d\n", dev->name, drv->ver.major, drv->ver.minor);
+
+	return ret;
+}
+
+static const struct udevice_id qcom_rpmh_ids[] = {
+	{ .compatible = "qcom,rpmh-rsc" },
+	{ }
+};
+
+U_BOOT_DRIVER(qcom_rpmh_rsc) = {
+	.name		= "qcom_rpmh_rsc",
+	.id		= UCLASS_MISC,
+	.priv_auto	= sizeof(struct rsc_drv),
+	.probe		= rpmh_rsc_probe,
+	.bind		= dm_scan_fdt_dev,
+	.of_match	= qcom_rpmh_ids,
+	/* rpmh is under CLUSTER_PD which we don't support */
+	.flags		= DM_FLAG_DEFAULT_PD_CTRL_OFF,
+};
diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c
new file mode 100644
index 000000000000..f00c241de373
--- /dev/null
+++ b/drivers/soc/qcom/rpmh.c
@@ -0,0 +1,110 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bug.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include <dm/device.h>
+
+#include <soc/qcom/rpmh.h>
+
+#include "rpmh-internal.h"
+
+#define RPMH_TIMEOUT_MS			msecs_to_jiffies(10000)
+
+#define DEFINE_RPMH_MSG_ONSTACK(device, s, name)	\
+	struct rpmh_request name = {			\
+		.msg = {				\
+			.state = s,			\
+			.cmds = name.cmd,		\
+			.num_cmds = 0,			\
+		},					\
+		.cmd = { { 0 } },			\
+		.dev = device,				\
+		.needs_free = false,			\
+	}
+
+#define ctrlr_to_drv(ctrlr) container_of(ctrlr, struct rsc_drv, client)
+
+static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct udevice *dev)
+{
+	struct rsc_drv *drv = (struct rsc_drv *)dev_get_priv(dev->parent);
+
+	if (!drv) {
+		printf("BUG: no RPMh driver for %s (parent %s)\n", dev->name, dev->parent->name);
+		BUG();
+	}
+
+	return &drv->client;
+}
+
+/**
+ * __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 udevice *dev, enum rpmh_state state,
+			struct rpmh_request *rpm_msg)
+{
+	struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
+
+	debug("rpmh_write: %s, %d\n", dev->name, state);
+
+	if (state != RPMH_ACTIVE_ONLY_STATE) {
+		printf("WARN: only ACTIVE_ONLY state supported\n");
+		return -EINVAL;
+	}
+
+	return rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg);
+}
+
+static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state,
+			   const struct tcs_cmd *cmd, u32 n)
+{
+	if (!cmd || !n || n > MAX_RPMH_PAYLOAD)
+		return -EINVAL;
+
+	memcpy(req->cmd, cmd, n * sizeof(*cmd));
+
+	req->msg.state = state;
+	req->msg.cmds = req->cmd;
+	req->msg.num_cmds = n;
+
+	debug("rpmh_msg: %d, %d cmds [first %#x/%#x]\n", state, n, cmd->addr, cmd->data);
+
+	return 0;
+}
+
+/**
+ * rpmh_write: Write a set of RPMH commands and block until response
+ *
+ * @dev: The device making the request
+ * @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 udevice *dev, enum rpmh_state state,
+	       const struct tcs_cmd *cmd, u32 n)
+{
+	DEFINE_RPMH_MSG_ONSTACK(dev, state, rpm_msg);
+	int ret;
+
+	ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n);
+	if (ret)
+		return ret;
+
+	ret = __rpmh_write(dev, state, &rpm_msg);
+
+	return ret;
+}
diff --git a/include/soc/qcom/cmd-db.h b/include/soc/qcom/cmd-db.h
new file mode 100644
index 000000000000..1e22d86c66c9
--- /dev/null
+++ b/include/soc/qcom/cmd-db.h
@@ -0,0 +1,42 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. */
+
+#ifndef __QCOM_COMMAND_DB_H__
+#define __QCOM_COMMAND_DB_H__
+
+#include <linux/err.h>
+#include <dm/device.h>
+
+enum cmd_db_hw_type {
+	CMD_DB_HW_INVALID = 0,
+	CMD_DB_HW_MIN     = 3,
+	CMD_DB_HW_ARC     = CMD_DB_HW_MIN,
+	CMD_DB_HW_VRM     = 4,
+	CMD_DB_HW_BCM     = 5,
+	CMD_DB_HW_MAX     = CMD_DB_HW_BCM,
+	CMD_DB_HW_ALL     = 0xff,
+};
+
+#if IS_ENABLED(CONFIG_QCOM_COMMAND_DB)
+u32 cmd_db_read_addr(const char *resource_id);
+
+const void *cmd_db_read_aux_data(const char *resource_id, size_t *len);
+
+enum cmd_db_hw_type cmd_db_read_slave_id(const char *resource_id);
+
+int cmd_db_init(ofnode node);
+
+#else
+static inline u32 cmd_db_read_addr(const char *resource_id)
+{ return 0; }
+
+static inline const void *cmd_db_read_aux_data(const char *resource_id, size_t *len)
+{ return ERR_PTR(-ENODEV); }
+
+static inline enum cmd_db_hw_type cmd_db_read_slave_id(const char *resource_id)
+{ return -ENODEV; }
+
+static inline int cmd_db_ready(void)
+{ return -ENODEV; }
+#endif /* CONFIG_QCOM_COMMAND_DB */
+#endif /* __QCOM_COMMAND_DB_H__ */
diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h
new file mode 100644
index 000000000000..eb7c067cc5fd
--- /dev/null
+++ b/include/soc/qcom/rpmh.h
@@ -0,0 +1,29 @@ 
+/* 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 <linux/types.h>
+#include <linux/errno.h>
+#include <soc/qcom/tcs.h>
+#include <dm/device-internal.h>
+
+#if IS_ENABLED(CONFIG_QCOM_RPMH)
+int rpmh_write(const struct udevice *dev, enum rpmh_state state,
+	       const struct tcs_cmd *cmd, u32 n);
+
+#else
+
+static inline int rpmh_write(const struct udevice *dev, enum rpmh_state state,
+			     const struct tcs_cmd *cmd, u32 n)
+{ return -ENODEV; }
+
+#endif /* CONFIG_QCOM_RPMH */
+
+/* u-boot: no multithreading */
+#define rpmh_write_async(dev, state, cmd, n) rpmh_write(dev, state, cmd, n)
+
+#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..c8d28b052f1d
--- /dev/null
+++ b/include/soc/qcom/tcs.h
@@ -0,0 +1,78 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2016-2019, 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: ensure that this command is complete before returning.
+ *        Setting "wait" here only makes sense during rpmh_write_batch() for
+ *        active-only transfers, this is because:
+ *        rpmh_write() - Always waits.
+ *                       (DEFINE_RPMH_MSG_ONSTACK will set .wait_for_compl)
+ *        rpmh_write_async() - Never waits.
+ *                       (There's no request completion callback)
+ */
+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.
+ * @num_cmds:       the number of @cmds in this request
+ * @cmds:           an array of tcs_cmds
+ */
+struct tcs_request {
+	enum rpmh_state state;
+	u32 num_cmds;
+	struct tcs_cmd *cmds;
+};
+
+#define BCM_TCS_CMD_COMMIT_SHFT		30
+#define BCM_TCS_CMD_COMMIT_MASK		0x40000000
+#define BCM_TCS_CMD_VALID_SHFT		29
+#define BCM_TCS_CMD_VALID_MASK		0x20000000
+#define BCM_TCS_CMD_VOTE_X_SHFT		14
+#define BCM_TCS_CMD_VOTE_MASK		0x3fff
+#define BCM_TCS_CMD_VOTE_Y_SHFT		0
+#define BCM_TCS_CMD_VOTE_Y_MASK		0xfffc000
+
+/* Construct a Bus Clock Manager (BCM) specific TCS command */
+#define BCM_TCS_CMD(commit, valid, vote_x, vote_y)		\
+	(((commit) << BCM_TCS_CMD_COMMIT_SHFT) |		\
+	((valid) << BCM_TCS_CMD_VALID_SHFT) |			\
+	((cpu_to_le32(vote_x) &					\
+	BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_X_SHFT) |	\
+	((cpu_to_le32(vote_y) &					\
+	BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_Y_SHFT))
+
+#endif /* __SOC_QCOM_TCS_H__ */