[PATCH v3 7/9] firmware: arm_scmi: Add ACPI PCC transport

Sudeep Holla <[email protected]>
Newsgroups org.kernel.vger.arm-scmi,org.infradead.lists.linux-arm-kernel
Message-ID <[email protected]>
Introduce a new SCMI transport that uses ACPI PCCT (PCC) subspaces via
the Linux PCC mailbox layer. Parse ACPI _DSD data to map protocol
associations to PCC transport UIDs. Support common and
protocol-exclusive A2P channels, plus optional common or
protocol-exclusive P2A channels for notifications.

Key points:
- new CONFIG_ARM_SCMI_TRANSPORT_PCC option
- integration with SCMI core via scmi_desc and transport ops
- response and notification fetch from PCC shared memory
- ACPI device matching and registration via the ACPI transport macro

This enables SCMI to be exercised over PCC on ACPI platforms.

Signed-off-by: Sudeep Holla <[email protected]>
---
 drivers/firmware/arm_scmi/common.h            |  11 +
 drivers/firmware/arm_scmi/transports/Kconfig  |  13 +
 drivers/firmware/arm_scmi/transports/Makefile |   2 +
 drivers/firmware/arm_scmi/transports/pcc.c    | 791 ++++++++++++++++++++++++++
 include/linux/scmi_protocol.h                 |   1 +
 5 files changed, 818 insertions(+)

diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 1ab4543e0f4a..3a49ea40aea5 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -468,6 +468,17 @@ struct scmi_transport_core_operations {
 	const struct scmi_message_operations *msg;
 };
 
+struct scmi_dsd_info {
+	u32 protocol_id;
+	const char *const property_name;
+};
+
+static const struct scmi_dsd_info scmi_dsd_info_list[] __maybe_unused = {
+	{ SCMI_PROTOCOL_BASE, "arm-arml0001-transport-pcc"},
+	{ SCMI_PROTOCOL_POWERCAP, "arm-arml0001-protocol-pcap"},
+	{ SCMI_PROTOCOL_TELEMETRY, "arm-arml0001-protocol-telemetry"},
+};
+
 /**
  * struct scmi_transport_handle  - Transport instance handle
  * @supplier_get: A helper to retrieve the device descriptor, identifying the
diff --git a/drivers/firmware/arm_scmi/transports/Kconfig b/drivers/firmware/arm_scmi/transports/Kconfig
index 57eccf316e26..1054165576b3 100644
--- a/drivers/firmware/arm_scmi/transports/Kconfig
+++ b/drivers/firmware/arm_scmi/transports/Kconfig
@@ -77,6 +77,19 @@ config ARM_SCMI_TRANSPORT_OPTEE
 	  This driver can also be built as a module. If so, the module
 	  will be called scmi_transport_optee.
 
+config ARM_SCMI_TRANSPORT_PCC
+	tristate "SCMI transport based on ACPI PCC"
+	depends on PCC
+	select ARM_SCMI_HAVE_TRANSPORT
+	default y
+	help
+	  Enable ACPI PCC mailbox based transport for SCMI.
+
+	  If you want the ARM SCMI PROTOCOL stack to include support for a
+	  transport based on mailboxes, answer Y.
+	  This driver can also be built as a module. If so, the module
+	  will be called scmi_transport_pcc.
+
 config ARM_SCMI_TRANSPORT_VIRTIO
 	tristate "SCMI transport based on VirtIO"
 	depends on VIRTIO
diff --git a/drivers/firmware/arm_scmi/transports/Makefile b/drivers/firmware/arm_scmi/transports/Makefile
index 3ba3d3bee151..e7c4b8de6251 100644
--- a/drivers/firmware/arm_scmi/transports/Makefile
+++ b/drivers/firmware/arm_scmi/transports/Makefile
@@ -7,6 +7,8 @@ scmi_transport_mailbox-objs := mailbox.o
 obj-$(CONFIG_ARM_SCMI_TRANSPORT_MAILBOX) += scmi_transport_mailbox.o
 scmi_transport_optee-objs := optee.o
 obj-$(CONFIG_ARM_SCMI_TRANSPORT_OPTEE) += scmi_transport_optee.o
+scmi_transport_pcc-objs := pcc.o
+obj-$(CONFIG_ARM_SCMI_TRANSPORT_PCC) += scmi_transport_pcc.o
 scmi_transport_virtio-objs := virtio.o
 obj-$(CONFIG_ARM_SCMI_TRANSPORT_VIRTIO) += scmi_transport_virtio.o
 
diff --git a/drivers/firmware/arm_scmi/transports/pcc.c b/drivers/firmware/arm_scmi/transports/pcc.c
new file mode 100644
index 000000000000..337d551e3ad8
--- /dev/null
+++ b/drivers/firmware/arm_scmi/transports/pcc.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Message ACPI PCC
+ * Transport Driver
+ *
+ * This transport uses ACPI PCC (PCCT Type 3/4) subspaces via the Linux
+ * PCC mailbox controller to exchange SCMI messages over the standard
+ * SCMI Shared Memory Transport (SMT) layout.
+ *
+ * PCC subspace selection is conveyed via ACPI SCMI namespace device.
+ *
+ * Copyright (C) 2026
+ */
+
+#include <linux/acpi.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/hashtable.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <acpi/pcc.h>
+
+#include "../common.h"
+
+#define SCMI_TRANSPORT_PACKAGE_MAX_VERSION	(1)
+#define SCMI_PROTOCOL_PACKAGE_MAX_VERSION	(1)
+#define SCMI_TRANSPORT_SHARED_CHANNEL		BIT_ULL(0)
+#define SCMI_TRANSPORT_P2A_CHANNEL		BIT_ULL(1)
+#define SCMI_TRANSPORT_FLAGS_MASK	(SCMI_TRANSPORT_SHARED_CHANNEL | \
+					 SCMI_TRANSPORT_P2A_CHANNEL)
+#define SCMI_PCC_SHMEM_OVERHEAD		(sizeof(struct pcc_shared_mem) + \
+					 sizeof(u32))
+
+/*
+ * SCMI specification requires all parameters, message headers, return
+ * arguments or any protocol data to be expressed in little endian
+ * format only.
+ */
+struct pcc_shared_mem {
+	struct acpi_pcct_ext_pcc_shared_memory header;
+	u8 msg_payload[];
+};
+
+/**
+ * struct scmi_pcc - Structure representing a SCMI mailbox transport
+ *
+ * @cl: Mailbox Client
+ * @pchan: Transmit/Receive PCC/mailbox channel
+ * @cinfo: SCMI channel info
+ */
+struct scmi_pcc {
+	struct mbox_client cl;
+	struct pcc_mbox_chan *pchan;
+	struct scmi_chan_info *cinfo;
+};
+
+struct pcc_transport {
+	u32 uid;
+	u32 pcc_ss_id;
+	u32 protocol_id;
+	u32 flags;
+	struct hlist_node hnode;
+};
+
+struct pcc_transport_map {
+	struct fwnode_handle *fwnode;
+	struct list_head node;
+	DECLARE_HASHTABLE(table, ilog2(MAX_PCC_SUBSPACES / 8));
+};
+
+#define client_to_scmi_pcc(c) container_of(c, struct scmi_pcc, cl)
+
+static struct scmi_transport_core_operations *core;
+static LIST_HEAD(pcc_transport_maps);
+static DEFINE_MUTEX(pcc_transport_maps_lock);
+
+static void acpi_scmi_clear_transport_map(struct pcc_transport_map *map)
+{
+	struct pcc_transport *p;
+	struct hlist_node *tmp;
+	int idx;
+
+	hash_for_each_safe(map->table, idx, tmp, p, hnode) {
+		hash_del(&p->hnode);
+		kfree(p);
+	}
+}
+
+static void acpi_scmi_destroy_transport_map(struct pcc_transport_map *map)
+{
+	acpi_scmi_clear_transport_map(map);
+	if (map->fwnode)
+		fwnode_handle_put(map->fwnode);
+	kfree(map);
+}
+
+static void acpi_scmi_clear_transport_maps(void)
+{
+	struct pcc_transport_map *map, *tmp;
+
+	list_for_each_entry_safe(map, tmp, &pcc_transport_maps, node) {
+		list_del(&map->node);
+		acpi_scmi_destroy_transport_map(map);
+	}
+}
+
+static const union acpi_object *
+acpi_scmi_pkg_elements(const union acpi_object *obj, unsigned int count)
+{
+	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != count)
+		return NULL;
+
+	return obj->package.elements;
+}
+
+static int
+acpi_scmi_pkg_u64(const union acpi_object *pkg, unsigned int idx, u64 *val)
+{
+	const union acpi_object *elem = &pkg->package.elements[idx];
+
+	if (elem->type != ACPI_TYPE_INTEGER)
+		return -EINVAL;
+
+	*val = elem->integer.value;
+
+	return 0;
+}
+
+static int
+acpi_scmi_pkg_u32(const union acpi_object *pkg, unsigned int idx, u32 *val)
+{
+	u64 tmp;
+	int ret;
+
+	ret = acpi_scmi_pkg_u64(pkg, idx, &tmp);
+	if (ret)
+		return ret;
+	if (tmp > U32_MAX)
+		return -EINVAL;
+
+	*val = tmp;
+
+	return 0;
+}
+
+static int acpi_scmi_property(const union acpi_object *properties,
+			      unsigned int idx, const char **name,
+			      const union acpi_object **value)
+{
+	const union acpi_object *property;
+	const union acpi_object *elems;
+
+	property = &properties->package.elements[idx];
+	elems = acpi_scmi_pkg_elements(property, 2);
+	if (!elems)
+		return -EINVAL;
+	if (elems[0].type != ACPI_TYPE_STRING || !elems[0].string.pointer)
+		return -EINVAL;
+
+	*name = elems[0].string.pointer;
+	*value = &elems[1];
+
+	return 0;
+}
+
+static int
+acpi_scmi_dsd_parse_transport_package(struct pcc_transport_map *map,
+				      const union acpi_object *obj)
+{
+	const union acpi_object *elems;
+	u32 revision, pkg_cnt;
+	unsigned int common_a2p = 0, common_p2a = 0;
+	int idx;
+
+	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2 ||
+	    acpi_scmi_pkg_u32(obj, 0, &revision) ||
+	    acpi_scmi_pkg_u32(obj, 1, &pkg_cnt))
+		return -EINVAL;
+	if (revision != SCMI_TRANSPORT_PACKAGE_MAX_VERSION)
+		return -EINVAL;
+	if (obj->package.count != pkg_cnt + 2)
+		return -EINVAL;
+
+	for (idx = 0; idx < pkg_cnt; idx++) {
+		union acpi_object *pack = &obj->package.elements[idx + 2];
+		struct pcc_transport *p, *tmp;
+		u32 pcc_ss_id, uid;
+		u64 flags;
+
+		elems = acpi_scmi_pkg_elements(pack, 3);
+		if (!elems) {
+			pr_info("Invalid transport properties pkg %d\n", idx);
+			return -EINVAL;
+		}
+		if (acpi_scmi_pkg_u32(pack, 0, &pcc_ss_id) ||
+		    acpi_scmi_pkg_u32(pack, 1, &uid) ||
+		    acpi_scmi_pkg_u64(pack, 2, &flags))
+			return -EINVAL;
+		if (flags & ~SCMI_TRANSPORT_FLAGS_MASK)
+			return -EINVAL;
+
+		hash_for_each_possible(map->table, tmp, hnode, uid) {
+			if (tmp->uid == uid) {
+				pr_info("Duplicate UID %d\n", uid);
+				return -EEXIST;
+			}
+		}
+
+		p = kzalloc(sizeof(*p), GFP_KERNEL);
+		if (!p)
+			return -ENOMEM;
+
+		p->uid = uid;
+		p->pcc_ss_id = pcc_ss_id;
+		p->flags = flags;
+		if (p->flags & SCMI_TRANSPORT_SHARED_CHANNEL) {
+			p->protocol_id = SCMI_PROTOCOL_BASE;
+			if (p->flags & SCMI_TRANSPORT_P2A_CHANNEL)
+				common_p2a++;
+			else
+				common_a2p++;
+		}
+
+		hash_add(map->table, &p->hnode, uid);
+	}
+
+	if (common_a2p != 1 || common_p2a > 1)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+acpi_scmi_dsd_parse_protocol_subpackage(struct pcc_transport_map *map,
+					const union acpi_object *obj,
+					int prot_id)
+{
+	bool found, tx_found = false, rx_found = false;
+	u32 uid;
+	int idx, ret = 0;
+	struct pcc_transport *p;
+	unsigned int pkg_cnt = obj->package.count;
+
+	if (pkg_cnt > 2) {
+		pr_warn("Only 2 channels: one Tx and one Rx needed\n");
+		return -EINVAL;
+	}
+
+	for (idx = 0; idx < pkg_cnt; idx++) {
+		union acpi_object *pack = &obj->package.elements[idx];
+		u64 flags;
+
+		if (!acpi_scmi_pkg_elements(pack, 2) ||
+		    acpi_scmi_pkg_u32(pack, 0, &uid) ||
+		    acpi_scmi_pkg_u64(pack, 1, &flags))
+			return -EINVAL;
+		if (flags)
+			return -EINVAL;
+
+		found = false;
+		hash_for_each_possible(map->table, p, hnode, uid) {
+			if (p->uid != uid)
+				continue;
+
+			found = true;
+			if (p->flags & SCMI_TRANSPORT_SHARED_CHANNEL) {
+				pr_info("Invalid! %d channel is shared\n",
+					p->pcc_ss_id);
+				ret = -EINVAL;
+				break;
+			}
+			if (p->protocol_id && p->protocol_id != prot_id)
+				return -EINVAL;
+
+			if (p->flags & SCMI_TRANSPORT_P2A_CHANNEL) {
+				if (rx_found)
+					return -EINVAL;
+				rx_found = true;
+			} else {
+				if (tx_found)
+					return -EINVAL;
+				tx_found = true;
+			}
+			p->protocol_id = prot_id;
+			break;
+		}
+
+		if (ret)
+			return ret;
+		if (!found)
+			return -ENOENT;
+	}
+
+	return ret;
+}
+
+static int
+acpi_scmi_dsd_parse_protocol_package(struct pcc_transport_map *map,
+				     const union acpi_object *obj, int prot_id)
+{
+	const union acpi_object *elems;
+	const union acpi_object *pack;
+	u32 revision;
+	int ret;
+
+	elems = acpi_scmi_pkg_elements(obj, 3);
+	if (!elems || acpi_scmi_pkg_u32(obj, 0, &revision))
+		return -EINVAL;
+
+	pack = &elems[1];
+
+	if (revision != SCMI_PROTOCOL_PACKAGE_MAX_VERSION)
+		return -EINVAL;
+
+	if (pack->type != ACPI_TYPE_PACKAGE) {
+		pr_info("Invalid protocol transport package\n");
+		return -EINVAL;
+	}
+
+	/* Empty protocol specific transport package allowed */
+	if (pack->package.count != 0) {
+		ret = acpi_scmi_dsd_parse_protocol_subpackage(map, pack, prot_id);
+		if (ret)
+			return ret;
+	}
+
+	pack = &elems[2];
+	if (pack->type != ACPI_TYPE_PACKAGE) {
+		pr_info("Invalid protocol transport association package\n");
+		return -EINVAL;
+	}
+
+	if (pack->package.count != 0) {
+		pr_info("Non-empty association package not supported\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* ACPI SCMI _DSD UUID: "84a2d1c6-86b6-4199-8dac-9c17449d5e03" */
+static const guid_t acpi_scmi_uuid = GUID_INIT(0x84a2d1c6, 0x86b6, 0x4199,
+					       0x8d, 0xac, 0x9c, 0x17,
+					       0x44, 0x9d, 0x5e, 0x03);
+
+static int acpi_scmi_lookup_protocol_id(const char *name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(scmi_dsd_info_list); i++) {
+		if (!strcmp(name, scmi_dsd_info_list[i].property_name))
+			return scmi_dsd_info_list[i].protocol_id;
+	}
+	return -ENOENT;
+}
+
+static int acpi_scmi_parse_properties(struct pcc_transport_map *map,
+				      const union acpi_object *properties)
+{
+	bool transport_found = false;
+	int i;
+
+	if (properties->type != ACPI_TYPE_PACKAGE)
+		return -EINVAL;
+
+	for (i = 0; i < properties->package.count; i++) {
+		const union acpi_object *v;
+		const char *name;
+		int prot_id, ret;
+
+		ret = acpi_scmi_property(properties, i, &name, &v);
+		if (ret)
+			return ret;
+
+		prot_id = acpi_scmi_lookup_protocol_id(name);
+		if (prot_id < 0)
+			continue;
+		if (prot_id != SCMI_PROTOCOL_BASE)
+			continue;
+		if (v->type != ACPI_TYPE_PACKAGE)
+			return -EINVAL;
+		if (transport_found)
+			return -EEXIST;
+
+		ret = acpi_scmi_dsd_parse_transport_package(map, v);
+		if (ret)
+			return ret;
+		transport_found = true;
+	}
+
+	if (!transport_found)
+		return -ENOENT;
+
+	for (i = 0; i < properties->package.count; i++) {
+		const union acpi_object *v;
+		const char *name;
+		int prot_id, ret;
+
+		ret = acpi_scmi_property(properties, i, &name, &v);
+		if (ret)
+			return ret;
+
+		prot_id = acpi_scmi_lookup_protocol_id(name);
+		if (prot_id < 0 || prot_id == SCMI_PROTOCOL_BASE)
+			continue;
+		if (v->type != ACPI_TYPE_PACKAGE)
+			return -EINVAL;
+
+		ret = acpi_scmi_dsd_parse_protocol_package(map, v, prot_id);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int acpi_scmi_namespace_fwnode_parse(struct fwnode_handle *fwnode,
+					    struct pcc_transport_map *map)
+{
+	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_device *adev = to_acpi_device_node(fwnode);
+	union acpi_object *desc;
+	acpi_status status;
+	int i, ret = -ENOENT;
+
+	if (!adev->handle)
+		return -EINVAL;
+
+	status = acpi_evaluate_object_typed(adev->handle, "_DSD", NULL, &buf,
+					    ACPI_TYPE_PACKAGE);
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	desc = buf.pointer;
+	if (desc->package.count % 2)
+		goto out_free_inval;
+
+	/* Look for the device properties GUID. */
+	for (i = 0; i < desc->package.count; i += 2) {
+		const union acpi_object *guid;
+		const union acpi_object *properties;
+
+		guid = &desc->package.elements[i];
+		properties = &desc->package.elements[i + 1];
+
+		/*
+		 * The first element must be a GUID and the second one must be
+		 * a package.
+		 */
+		if (guid->type != ACPI_TYPE_BUFFER ||
+		    guid->buffer.length != UUID_SIZE ||
+		    properties->type != ACPI_TYPE_PACKAGE)
+			continue;
+
+		if (!guid_equal((guid_t *)guid->buffer.pointer,
+				&acpi_scmi_uuid))
+			continue;
+
+		ret = acpi_scmi_parse_properties(map, properties);
+		goto out_free;
+	}
+
+out_free:
+	ACPI_FREE(buf.pointer);
+	return ret;
+out_free_inval:
+	ret = -EINVAL;
+	goto out_free;
+}
+
+static bool
+pcc_transport_map_has_ss_id(struct pcc_transport_map *map, u32 pcc_ss_id)
+{
+	struct pcc_transport *p;
+	int idx;
+
+	hash_for_each(map->table, idx, p, hnode) {
+		if (p->pcc_ss_id == pcc_ss_id)
+			return true;
+	}
+
+	return false;
+}
+
+static int pcc_transport_map_validate(struct pcc_transport_map *map)
+{
+	struct pcc_transport_map *iter;
+	struct pcc_transport *p;
+	int idx;
+
+	list_for_each_entry(iter, &pcc_transport_maps, node) {
+		hash_for_each(map->table, idx, p, hnode) {
+			if (pcc_transport_map_has_ss_id(iter, p->pcc_ss_id))
+				return -EEXIST;
+		}
+	}
+
+	return 0;
+}
+
+static
+struct pcc_transport_map *pcc_transport_map_find(struct fwnode_handle *fwnode)
+{
+	struct pcc_transport_map *map;
+
+	list_for_each_entry(map, &pcc_transport_maps, node) {
+		if (map->fwnode == fwnode)
+			return map;
+	}
+
+	return NULL;
+}
+
+static
+struct pcc_transport_map *pcc_transport_map_get(struct fwnode_handle *fwnode)
+{
+	struct pcc_transport_map *map;
+	int ret;
+
+	map = pcc_transport_map_find(fwnode);
+	if (map)
+		return map;
+
+	map = kzalloc_obj(*map, GFP_KERNEL);
+	if (!map)
+		return ERR_PTR(-ENOMEM);
+
+	hash_init(map->table);
+	ret = acpi_scmi_namespace_fwnode_parse(fwnode, map);
+	if (ret)
+		goto err_free_map;
+
+	ret = pcc_transport_map_validate(map);
+	if (ret)
+		goto err_free_map;
+
+	map->fwnode = fwnode_handle_get(fwnode);
+	list_add_tail(&map->node, &pcc_transport_maps);
+
+	return map;
+
+err_free_map:
+	acpi_scmi_destroy_transport_map(map);
+	return ERR_PTR(ret);
+}
+
+static int pcc_lookup_ss_id(struct pcc_transport_map *map, u32 prot_id, bool tx)
+{
+	struct pcc_transport *p;
+	int idx;
+
+	hash_for_each(map->table, idx, p, hnode) {
+		if (p->protocol_id != prot_id)
+			continue;
+
+		if ((!tx && (p->flags & SCMI_TRANSPORT_P2A_CHANNEL)) ||
+		    (tx && !(p->flags & SCMI_TRANSPORT_P2A_CHANNEL)))
+			return p->pcc_ss_id;
+	}
+
+	return -ENOENT;
+}
+
+static int pcc_get_ss_id(struct fwnode_handle *fwnode, u32 prot_id, bool tx)
+{
+	struct pcc_transport_map *map;
+	int ret;
+
+	if (!fwnode)
+		return -EINVAL;
+
+	mutex_lock(&pcc_transport_maps_lock);
+	map = pcc_transport_map_get(fwnode);
+	if (IS_ERR(map))
+		ret = PTR_ERR(map);
+	else
+		ret = pcc_lookup_ss_id(map, prot_id, tx);
+	mutex_unlock(&pcc_transport_maps_lock);
+
+	return ret;
+}
+
+static bool
+pcc_chan_available(struct fwnode_handle *fwnode, int prot_id, int idx)
+{
+	if (pcc_get_ss_id(fwnode, prot_id, idx ? false : true) < 0)
+		return false;
+
+	return true;
+}
+
+static void tx_prepare(struct mbox_client *cl, void *m)
+{
+	struct scmi_pcc *smbox = client_to_scmi_pcc(cl);
+	struct pcc_shared_mem __iomem *shmem = smbox->pchan->shmem;
+	struct scmi_xfer *xfer = m;
+
+	/*
+	 * PCC take cares not to call tx_prepare until last transmit is done.
+	 * Request platform interrupt notification if available.
+	 */
+	iowrite32(smbox->pchan->mchan->mbox->txdone_irq ?
+		  PCC_CMD_COMPLETION_NOTIFY : 0, &shmem->header.flags);
+	iowrite32(sizeof(shmem->header.command) + xfer->tx.len,
+		  &shmem->header.length);
+	iowrite32(pack_scmi_header(&xfer->hdr), &shmem->header.command);
+	if (xfer->tx.buf)
+		memcpy_toio(shmem->msg_payload, xfer->tx.buf, xfer->tx.len);
+}
+
+static void rx_callback(struct mbox_client *cl, void *m)
+{
+	struct scmi_pcc *smbox = client_to_scmi_pcc(cl);
+	struct pcc_shared_mem __iomem *shmem = smbox->pchan->shmem;
+
+	core->rx_callback(smbox->cinfo, ioread32(&shmem->header.command), NULL);
+}
+
+static int pcc_chan_validate_shmem(struct scmi_chan_info *cinfo,
+				   struct scmi_pcc *smbox)
+{
+	if (smbox->pchan->shmem_size < SCMI_PCC_SHMEM_OVERHEAD ||
+	    smbox->pchan->shmem_size - SCMI_PCC_SHMEM_OVERHEAD <
+	    cinfo->max_msg_size) {
+		dev_err(cinfo->dev, "misconfigured SCMI PCC shared memory\n");
+		return -ENOSPC;
+	}
+
+	return 0;
+}
+
+static int pcc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
+			  bool tx)
+{
+	const char *desc = tx ? "Tx" : "Rx";
+	struct device *cdev = cinfo->dev;
+	struct scmi_pcc *smbox;
+	int ret, ss_id;
+	struct mbox_client *cl;
+
+	smbox = devm_kzalloc(dev, sizeof(*smbox), GFP_KERNEL);
+	if (!smbox)
+		return -ENOMEM;
+
+	cl = &smbox->cl;
+	cl->dev = cdev;
+	cl->tx_prepare = tx ? tx_prepare : NULL;
+	cl->rx_callback = rx_callback;
+	cl->tx_block = false;
+
+	ss_id = pcc_get_ss_id(dev_fwnode(cinfo->dev), cinfo->id, tx);
+	if (ss_id < 0)
+		return ss_id;
+
+	smbox->pchan = pcc_mbox_request_channel(cl, ss_id);
+	if (IS_ERR(smbox->pchan)) {
+		ret = PTR_ERR(smbox->pchan);
+		if (ret != -EPROBE_DEFER)
+			dev_err(cdev,
+				"failed to request SCMI %s mailbox\n", desc);
+		return ret;
+	}
+
+	ret = pcc_chan_validate_shmem(cinfo, smbox);
+	if (ret) {
+		pcc_mbox_free_channel(smbox->pchan);
+		return ret;
+	}
+
+	cinfo->transport_info = smbox;
+	smbox->cinfo = cinfo;
+
+	return 0;
+}
+
+static int pcc_chan_free(int id, void *p, void *data)
+{
+	struct scmi_chan_info *cinfo = p;
+	struct scmi_pcc *smbox = cinfo->transport_info;
+
+	if (smbox && !IS_ERR(smbox->pchan)) {
+		pcc_mbox_free_channel(smbox->pchan);
+		cinfo->transport_info = NULL;
+		smbox->pchan = NULL;
+		smbox->cinfo = NULL;
+	}
+
+	return 0;
+}
+
+static int
+pcc_send_message(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer)
+{
+	struct scmi_pcc *smbox = cinfo->transport_info;
+	int ret;
+
+	/*
+	 * The mailbox layer has its own queue. However the mailbox queue
+	 * confuses the per message SCMI timeouts since the clock starts when
+	 * the message is submitted into the mailbox queue. So when multiple
+	 * messages are queued up the clock starts on all messages instead of
+	 * only the one inflight.
+	 */
+	ret = mbox_send_message(smbox->pchan->mchan, xfer);
+	/* mbox_send_message returns non-negative value on success */
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void pcc_fetch_response(struct scmi_chan_info *cinfo,
+			       struct scmi_xfer *xfer)
+{
+	struct scmi_pcc *smbox = cinfo->transport_info;
+	struct pcc_shared_mem __iomem *shmem = smbox->pchan->shmem;
+	size_t len = ioread32(&shmem->header.length);
+
+	xfer->hdr.status = ioread32(shmem->msg_payload);
+	/* Skip the length of header and status in shmem area i.e 8 bytes */
+	xfer->rx.len = min_t(size_t, xfer->rx.len, len > 8 ? len - 8 : 0);
+
+	/* Take a copy to the rx buffer.. */
+	memcpy_fromio(xfer->rx.buf, shmem->msg_payload + 4, xfer->rx.len);
+}
+
+static void pcc_fetch_notification(struct scmi_chan_info *cinfo, size_t max_len,
+				   struct scmi_xfer *xfer)
+{
+	struct scmi_pcc *smbox = cinfo->transport_info;
+	struct pcc_shared_mem __iomem *shmem = smbox->pchan->shmem;
+	size_t len = ioread32(&shmem->header.length);
+
+	/* Skip only the length of header in shmem area i.e 4 bytes */
+	xfer->rx.len = min_t(size_t, max_len, len > 4 ? len - 4 : 0);
+
+	/* Take a copy to the rx buffer.. */
+	memcpy_fromio(xfer->rx.buf, shmem->msg_payload, xfer->rx.len);
+}
+
+static const struct scmi_transport_ops scmi_pcc_ops = {
+	.chan_available = pcc_chan_available,
+	.chan_setup = pcc_chan_setup,
+	.chan_free = pcc_chan_free,
+	.send_message = pcc_send_message,
+	.fetch_response = pcc_fetch_response,
+	.fetch_notification = pcc_fetch_notification,
+};
+
+static struct scmi_desc scmi_pcc_desc = {
+	.ops = &scmi_pcc_ops,
+	.max_rx_timeout_ms = 30,	/* We may increase this if required */
+	.max_msg = 20,		/* Limited by MBOX_TX_QUEUE_LEN */
+	.max_msg_size = SCMI_SHMEM_MAX_PAYLOAD_SIZE - 12,
+};
+
+static const struct acpi_device_id scmi_acpi_ids[] = {
+	{ "ARML0001", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(acpi, scmi_acpi_ids);
+
+DEFINE_SCMI_ACPI_TRANSPORT_DRIVER(scmi_pcc, scmi_pcc_driver,
+				  scmi_pcc_desc, scmi_acpi_ids, core);
+
+static int __init scmi_pcc_init(void)
+{
+	return platform_driver_register(&scmi_pcc_driver);
+}
+
+static void __exit scmi_pcc_exit(void)
+{
+	platform_driver_unregister(&scmi_pcc_driver);
+
+	mutex_lock(&pcc_transport_maps_lock);
+	acpi_scmi_clear_transport_maps();
+	mutex_unlock(&pcc_transport_maps_lock);
+}
+module_init(scmi_pcc_init);
+module_exit(scmi_pcc_exit);
+
+MODULE_AUTHOR("Sudeep Holla <[email protected]>");
+MODULE_DESCRIPTION("SCMI ACPI PCC Transport driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 5ab73b1ab9aa..02cf04543151 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -930,6 +930,7 @@ enum scmi_std_protocol {
 	SCMI_PROTOCOL_VOLTAGE = 0x17,
 	SCMI_PROTOCOL_POWERCAP = 0x18,
 	SCMI_PROTOCOL_PINCTRL = 0x19,
+	SCMI_PROTOCOL_TELEMETRY = 0x1B,
 };
 
 enum scmi_system_events {

-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.