diff mbox series

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

Message ID 20240617-b4-qcom-rpmh-v1-2-bd2336923e0a@linaro.org
State Superseded
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
>
Neil Armstrong June 28, 2024, 3:38 p.m. UTC | #3
Hi Caleb,

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>

> +
> +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) {

I had to change to:
			if (strncmp(ent->id, query, sizeof(ent->id)) == 0) {
otherwise I get:
Failed to read RPMh address for bobb1
...

on SM8550 and SM8650.

Linux uses memcmp, but it pads the buffer with strtomem_pad(), strncpy doesn't seem to do the padding.
	
Neil

> +				if (eh)
> +					*eh = ent;
> +				if (rh)
> +					*rh = rsc_hdr;
> +				return 0;
> +			}
> +		}
> +	}
> +
> +	return -ENODEV;
> +}

<snip>
Caleb Connolly July 8, 2024, 10:43 a.m. UTC | #4
Hi Sumit,

Sorry for the late response.

In general, I'd rather keep this port closer to the Linux original, so 
that future cherry-picking of new features and fixes is easier. This is 
quite standard practice in U-Boot (and why the compatibility macro's are 
included in the first place).

Otherwise, we end up with our own quite different implementation, with 
the higher maintenance burden that comes with it.

This port doesn't do a perfect job at keeping close to upstream, but 
still I don't think we should stray further without a good justification.

Kind regards,

>> +EXPORT_SYMBOL(cmd_db_read_addr);
> 
> Export symbols aren't required in U-Boot here and other instances in this patch.

...

>> +       /*
>> +        * 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?

...
>> +       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?

...

>> +
>> +       spin_lock_irqsave(&drv->lock, flags);
> 
> Locks aren't needed in U-Boot, can be dropped here and other places.
> 

...

>> +
>> +       spin_lock_init(&drv->lock);
>> +       init_waitqueue_head(&drv->tcs_wait);
> 
> Similarly waitqueue should be dropped too.
> 
> -Sumit
Sumit Garg July 9, 2024, 5:28 a.m. UTC | #5
Hi Caleb,

On Mon, 8 Jul 2024 at 16:13, Caleb Connolly <caleb.connolly@linaro.org> wrote:
>
> Hi Sumit,
>
> Sorry for the late response.
>
> In general, I'd rather keep this port closer to the Linux original, so
> that future cherry-picking of new features and fixes is easier. This is
> quite standard practice in U-Boot (and why the compatibility macro's are
> included in the first place).
>
> Otherwise, we end up with our own quite different implementation, with
> the higher maintenance burden that comes with it.
>
> This port doesn't do a perfect job at keeping close to upstream, but
> still I don't think we should stray further without a good justification.
>

As per your cover letter:

Quote:
   "These drivers were originally taken from Linux, however they have been
    heavily modified and simplified for U-Boot. Since the original Linux
    drivers contained heavy optimisations related to multithreading and
    asynchronous probing, as well as support for idle and suspend states
    which we don't need to deal with here."

This made me think the driver is nowhere close to Linux driver
functionality wise. Also, the commit message doesn't say which Linux
commit this driver is influenced from. I am not really sure if
cherry-picking will be straight forward. IMHO, if we are heavily
modifying a driver port for U-Boot then it should be done properly as
per U-Boot needs. A middle ground approach which you have taken is
only going to lead to confusion and rather negatively impact
maintenance.

-Sumit

> Kind regards,
>
> >> +EXPORT_SYMBOL(cmd_db_read_addr);
> >
> > Export symbols aren't required in U-Boot here and other instances in this patch.
>
> ...
>
> >> +       /*
> >> +        * 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?
>
> ...
> >> +       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?
>
> ...
>
> >> +
> >> +       spin_lock_irqsave(&drv->lock, flags);
> >
> > Locks aren't needed in U-Boot, can be dropped here and other places.
> >
>
> ...
>
> >> +
> >> +       spin_lock_init(&drv->lock);
> >> +       init_waitqueue_head(&drv->tcs_wait);
> >
> > Similarly waitqueue should be dropped too.
> >
> > -Sumit
>
> --
> // Caleb (they/them)
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__ */