[RFC PATCH 4/7] firmware: arm_scmi: Add System Telemetry driver

Cristian Marussi cristian.marussi at arm.com
Fri Jun 20 12:28:10 PDT 2025


Add an SCMI System Telemetry driver which exposes a sysfs custom API to
configure and consume Telemetry data from userspace.

Still lacking:
 - complete groups handling
 - ungrouped DEs handling

Signed-off-by: Cristian Marussi <cristian.marussi at arm.com>
---
 drivers/firmware/arm_scmi/Kconfig             |  10 +
 drivers/firmware/arm_scmi/Makefile            |   1 +
 .../firmware/arm_scmi/scmi_system_telemetry.c | 967 ++++++++++++++++++
 3 files changed, 978 insertions(+)
 create mode 100644 drivers/firmware/arm_scmi/scmi_system_telemetry.c

diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/Kconfig
index e3fb36825978..9e51b3cd0c93 100644
--- a/drivers/firmware/arm_scmi/Kconfig
+++ b/drivers/firmware/arm_scmi/Kconfig
@@ -99,4 +99,14 @@ config ARM_SCMI_POWER_CONTROL
 	  called scmi_power_control. Note this may needed early in boot to catch
 	  early shutdown/reboot SCMI requests.
 
+config ARM_SCMI_SYSTEM_TELEMETRY
+	tristate "SCMI System Telemetry driver"
+	depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+	help
+	  This enables SCMI Systemn Telemetry support that allows userspace to
+	  retrieve ARM Telemetry data made available via SCMI.
+
+	  This driver can also be built as a module.  If so, the module will be
+	  called scmi_system_telemetry.
+
 endmenu
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index fe55b7aa0707..20f8d55840a5 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-core.o
 obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o
 
 obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) += scmi_power_control.o
+obj-$(CONFIG_ARM_SCMI_SYSTEM_TELEMETRY) += scmi_system_telemetry.o
diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
new file mode 100644
index 000000000000..a2f59001747d
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -0,0 +1,967 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCMI - System Telemetry Driver
+ *
+ * Copyright (C) 2025 ARM Ltd.
+ */
+
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/scmi_protocol.h>
+#include <linux/sysfs.h>
+#include <linux/slab.h>
+
+#define MAX_BULK_LINE_CHAR_LENGTH	64
+
+struct scmi_tlm_setup;
+
+static atomic_t scmi_tlm_instance_count = ATOMIC_INIT(0);
+
+struct scmi_tlm_grp_dev {
+	struct device dev;
+	struct scmi_tlm_setup *tsp;
+	const struct scmi_telemetry_group *grp;
+};
+
+#define to_tlm_grp_dev(d)					\
+	(container_of((d), struct scmi_tlm_grp_dev, dev))
+
+struct scmi_tlm_de_dev {
+	struct device dev;
+	struct scmi_tlm_setup *tsp;
+	const struct scmi_telemetry_de *de;
+};
+
+#define to_tlm_de_dev(d)					\
+	(container_of((d), struct scmi_tlm_de_dev, dev))
+
+struct scmi_tlm_instance {
+	struct device dev;
+	struct device des_dev;
+	struct device groups_dev;
+	struct scmi_tlm_de_dev **des;
+	struct scmi_tlm_setup *tsp;
+	const struct scmi_telemetry_info *info;
+};
+
+#define dev_to_tlm_instance(d)	\
+	(container_of((d), struct scmi_tlm_instance, dev))
+
+#define des_dev_to_tlm_instance(e)	\
+	(container_of((e), struct scmi_tlm_instance, des_dev))
+
+#define groups_dev_to_tlm_instance(e)	\
+	(container_of((e), struct scmi_tlm_instance, groups_dev))
+
+/**
+ * struct scmi_tlm_setup  - Telemetry setup descriptor
+ * @sdev: A reference to the related SCMI device
+ * @ops: A reference to the protocol ops
+ * @ph: A reference to the protocol handle to be used with the ops
+ * @priv: A reference to optional driver-specific data
+ */
+struct scmi_tlm_setup {
+	struct scmi_device *sdev;
+	const struct scmi_telemetry_proto_ops *ops;
+	struct scmi_protocol_handle *ph;
+	const void *priv;
+};
+
+static void scmi_telemetry_release(struct device *dev)
+{
+}
+
+static ssize_t __all_enable_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t len,
+				  bool is_enable_entry)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	bool enable;
+	int ret;
+
+	if (kstrtobool(buf, &enable))
+		return -EINVAL;
+
+	if (is_enable_entry && !enable) {
+		ret = tsp->ops->all_disable(tsp->ph, false);
+		if (ret)
+			return ret;
+	} else {
+		for (int i = 0; i < ti->info->num_de; i++) {
+			const struct scmi_telemetry_de *de = ti->info->des[i];
+
+			ret = tsp->ops->state_set(tsp->ph, false, de->id,
+						   is_enable_entry ? &enable : NULL,
+						   !is_enable_entry ? &enable : NULL);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return len;
+}
+
+static ssize_t all_des_enable_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t len)
+{
+	return __all_enable_store(dev, attr, buf, len, true);
+}
+
+static ssize_t all_des_tstamp_enable_store(struct device *dev,
+					   struct device_attribute *attr,
+					   const char *buf, size_t len)
+{
+	return __all_enable_store(dev, attr, buf, len, false);
+}
+
+static inline ssize_t __current_update_show(char *buf,
+					    unsigned int active_update_interval)
+{
+	return sysfs_emit(buf, "%u\n",
+			  SCMI_GET_UPDATE_INTERVAL_SECS(active_update_interval));
+}
+
+static inline ssize_t __current_update_store(struct scmi_tlm_setup *tsp,
+					     const char *buf, size_t len,
+					     unsigned int grp_id)
+{
+	bool grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
+	unsigned int update_interval_ms = 0;
+	int ret;
+
+	ret = kstrtouint(buf, 0, &update_interval_ms);
+	if (ret)
+		return ret;
+
+	ret = tsp->ops->collection_configure(tsp->ph, grp_id, grp_ignore, NULL,
+					     &update_interval_ms, NULL);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t current_update_interval_ms_show(struct device *dev,
+					       struct device_attribute *attr,
+					       char *buf)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+	return __current_update_show(buf,
+				     ti->info->intervals.active_update_interval);
+}
+
+static ssize_t current_update_interval_ms_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t len)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+	struct scmi_tlm_setup *tsp = ti->tsp;
+
+	return __current_update_store(tsp, buf, len, SCMI_TLM_GRP_INVALID);
+}
+
+static ssize_t tlm_enable_show(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+	return sysfs_emit(buf, "%c\n", ti->info->enabled ? 'Y' : 'N');
+}
+
+static ssize_t tlm_enable_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t len)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	enum scmi_telemetry_collection mode = SCMI_TLM_ONDEMAND;
+	bool enabled;
+	int ret;
+
+	ret = kstrtobool(buf, &enabled);
+	if (ret)
+		return ret;
+
+	ret = tsp->ops->collection_configure(tsp->ph, SCMI_TLM_GRP_INVALID, true,
+					     &enabled, NULL, &mode);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static int scmi_tlm_buffer_fill(struct device *dev, char *buf, size_t size,
+				int *len, int num,
+				struct scmi_telemetry_de_sample *samples)
+{
+	int idx, bytes = 0;
+
+	/* Loop till there space for the next line */
+	for (idx = 0; idx < num && size - bytes >= MAX_BULK_LINE_CHAR_LENGTH; idx++) {
+		bytes += snprintf(buf + bytes, size - bytes,
+				  "0x%04X %llu %016llX\n", samples[idx].id,
+				  samples[idx].tstamp, samples[idx].val);
+	}
+
+	if (idx < num) {
+		dev_err(dev, "Bulk buffer truncated !\n");
+		return -ENOSPC;
+	}
+
+	if (len)
+		*len = bytes;
+
+	return 0;
+}
+
+static inline ssize_t __des_bulk_read_show(struct scmi_tlm_instance *ti,
+					   unsigned int grp_id, char *buf,
+					   int size)
+{
+	struct scmi_telemetry_de_sample *samples;
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	int ret, num;
+
+	num = ti->info->num_de;
+	samples = kcalloc(num, sizeof(*samples), GFP_KERNEL);
+	if (!samples)
+		return -ENOMEM;
+
+	ret = tsp->ops->des_bulk_read(tsp->ph, grp_id, &num, samples);
+	if (ret) {
+		kfree(samples);
+		return ret;
+	}
+
+	ret = scmi_tlm_buffer_fill(&ti->dev, buf, size, NULL, num, samples);
+	kfree(samples);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%s", buf);
+}
+
+static ssize_t des_bulk_read_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+	return __des_bulk_read_show(ti, SCMI_TLM_GRP_INVALID, buf, PAGE_SIZE);
+}
+
+static inline ssize_t __des_single_sample_read_show(struct scmi_tlm_instance *ti,
+						    unsigned int grp_id,
+						    char *buf, int len)
+{
+	struct scmi_telemetry_de_sample *samples;
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	int ret, num, bytes = 0;
+
+	num = ti->info->num_de;
+	samples = kcalloc(num, sizeof(*samples), GFP_KERNEL);
+	if (!samples)
+		return -ENOMEM;
+
+	ret = tsp->ops->des_sample_get(tsp->ph, grp_id, &num, samples);
+	if (ret) {
+		kfree(samples);
+		return ret;
+	}
+
+	for (int i = 0; i < num; i++) {
+		bytes += snprintf(buf + bytes, len - bytes,
+				  "0x%04X %llu %016llX\n", samples[i].id,
+				  samples[i].tstamp, samples[i].val);
+
+		if (bytes >= len) {
+			dev_err(&ti->dev, "==>> BULK BUFFER OVERFLOW !\n");
+			kfree(samples);
+			return -ENOSPC;
+		}
+	}
+
+	kfree(samples);
+
+	return sysfs_emit(buf, "%s", buf);
+}
+
+static ssize_t des_single_sample_read_show(struct device *dev,
+					   struct device_attribute *attr, char *buf)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+	return __des_single_sample_read_show(ti, SCMI_TLM_GRP_INVALID,
+					     buf, PAGE_SIZE);
+}
+
+static ssize_t de_implementation_version_show(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+	return sysfs_emit(buf, "%pUL\n", ti->info->de_impl_version);
+}
+
+static inline ssize_t __intervals_discrete_show(char *buf, const bool discrete)
+{
+	return sysfs_emit(buf, "%c\n", discrete ? 'Y' : 'N');
+}
+
+static ssize_t intervals_discrete_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+	return __intervals_discrete_show(buf, ti->info->intervals.discrete);
+}
+
+//TODO Review available interval show
+#define BUF_SZ	1024
+static inline ssize_t
+__available_update_show(char *buf,
+			const struct scmi_telemetry_update_interval *intervals)
+{
+	int len = 0, num_intervals = intervals->num;
+	char available[BUF_SZ];
+
+	for (int i = 0; i < num_intervals; i++) {
+		len += scnprintf(available + len, BUF_SZ - len, "%u ",
+				 intervals->update_intervals[i]);
+	}
+
+	available[len - 1] = '\0';
+
+	return sysfs_emit(buf, "%s\n", available);
+}
+
+static ssize_t available_update_intervals_ms_show(struct device *dev,
+						  struct device_attribute *attr,
+						  char *buf)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+	return __available_update_show(buf, &ti->info->intervals);
+}
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+	return sysfs_emit(buf, "0x%08x\n", ti->info->version);
+}
+
+static DEVICE_ATTR_WO(all_des_enable);
+static DEVICE_ATTR_WO(all_des_tstamp_enable);
+static DEVICE_ATTR_RW(current_update_interval_ms);
+static DEVICE_ATTR_RW(tlm_enable);
+static DEVICE_ATTR_RO(des_bulk_read);
+static DEVICE_ATTR_RO(des_single_sample_read);
+static DEVICE_ATTR_RO(de_implementation_version);
+static DEVICE_ATTR_RO(intervals_discrete);
+static DEVICE_ATTR_RO(available_update_intervals_ms);
+static DEVICE_ATTR_RO(version);
+
+static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t len)
+{
+	struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+	int ret;
+
+	ret = ti->tsp->ops->reset(ti->tsp->ph);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static struct device_attribute dev_attr_reset = {
+	.attr	= { .name = "reset", .mode = 0200 },
+	.store	= reset_store,
+};
+
+static struct attribute *scmi_telemetry_attrs[] = {
+	&dev_attr_all_des_enable.attr,
+	&dev_attr_all_des_tstamp_enable.attr,
+	&dev_attr_current_update_interval_ms.attr,
+	&dev_attr_tlm_enable.attr,
+	&dev_attr_des_bulk_read.attr,
+	&dev_attr_des_single_sample_read.attr,
+	&dev_attr_de_implementation_version.attr,
+	&dev_attr_intervals_discrete.attr,
+	&dev_attr_available_update_intervals_ms.attr,
+	&dev_attr_version.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(scmi_telemetry);
+
+static struct class scmi_telemetry_class = {
+	.name = "scmi_telemetry",
+	.dev_release = scmi_telemetry_release,
+};
+
+static ssize_t value_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev);
+	struct scmi_tlm_setup *tsp = tde->tsp;
+	struct scmi_telemetry_de_sample sample;
+	int ret;
+
+	sample.id = tde->de->id;
+	ret = tsp->ops->de_data_read(tsp->ph, &sample);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%llu: %016llX\n", sample.tstamp, sample.val);
+}
+static DEVICE_ATTR_RO(value);
+
+#define DEFINE_DE_ATTR_INT_RO(_attr, _fmt)				\
+static ssize_t _attr##_show(struct device *dev,				\
+			    struct device_attribute *attr,		\
+			    char *buf)					\
+{									\
+	struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev);		\
+									\
+	return sysfs_emit(buf, _fmt "\n", tde->de->_attr);		\
+}									\
+static DEVICE_ATTR_RO(_attr)
+
+#define DEFINE_DE_ATTR_BOOL_RO(_attr)					\
+static ssize_t _attr##_show(struct device *dev,				\
+			    struct device_attribute *attr,		\
+			    char *buf)					\
+{									\
+	struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev);		\
+									\
+	return sysfs_emit(buf, "%c\n", tde->de->_attr ? 'Y' : 'N');	\
+}									\
+static DEVICE_ATTR_RO(_attr)
+
+DEFINE_DE_ATTR_INT_RO(type, "%u");
+DEFINE_DE_ATTR_INT_RO(unit, "%u");
+DEFINE_DE_ATTR_INT_RO(unit_exp, "%d");
+DEFINE_DE_ATTR_INT_RO(instance_id, "%u");
+DEFINE_DE_ATTR_INT_RO(compo_type, "%u");
+DEFINE_DE_ATTR_INT_RO(compo_instance_id, "%u");
+DEFINE_DE_ATTR_BOOL_RO(persistent);
+DEFINE_DE_ATTR_INT_RO(name, "%s");
+DEFINE_DE_ATTR_INT_RO(tstamp_exp, "%d");
+
+#define DEFINE_DE_ATTR_STATE_RW(_name, _is_enable)			\
+static ssize_t _name##_store(struct device *dev,			\
+			     struct device_attribute *attr,		\
+			     const char *buf, size_t len)		\
+{									\
+	struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev);		\
+	struct scmi_tlm_setup *tsp = tde->tsp;				\
+	typeof(_is_enable) _is_ena = _is_enable;			\
+	bool enabled;							\
+	int ret;							\
+									\
+	ret = kstrtobool(buf, &enabled);				\
+	if (ret)							\
+		return ret;						\
+									\
+	ret = tsp->ops->state_set(tsp->ph, false, tde->de->id,		\
+				  _is_ena ? &enabled : NULL,		\
+				  !_is_ena ? &enabled : NULL);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return len;							\
+}									\
+									\
+static ssize_t _name##_show(struct device *dev,				\
+			    struct device_attribute *attr, char *buf)	\
+{									\
+	struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev);		\
+									\
+	return sysfs_emit(buf, "%c\n", tde->de->_name ## d ? 'Y' : 'N');\
+}									\
+static DEVICE_ATTR_RW(_name)
+
+DEFINE_DE_ATTR_STATE_RW(enable, true);
+DEFINE_DE_ATTR_STATE_RW(tstamp_enable, false);
+
+static struct attribute *scmi_des_attrs[] = {
+	&dev_attr_value.attr,
+	&dev_attr_type.attr,
+	&dev_attr_unit.attr,
+	&dev_attr_unit_exp.attr,
+	&dev_attr_instance_id.attr,
+	&dev_attr_compo_type.attr,
+	&dev_attr_compo_instance_id.attr,
+	&dev_attr_persistent.attr,
+	&dev_attr_enable.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(scmi_des);
+
+static void scmi_tlm_dev_release(struct device *dev)
+{
+}
+
+static int
+scmi_telemetry_dev_register(struct device *dev, struct device *parent,
+			    const char *name)
+{
+	int ret;
+
+	dev->parent = parent;
+	dev->release = scmi_tlm_dev_release;
+	dev_set_name(dev, "%s", name);
+	device_set_pm_not_required(dev);
+	dev_set_uevent_suppress(dev, true);
+	ret = device_register(dev);
+	if (ret)
+		put_device(dev);
+
+	return ret;
+}
+
+static int scmi_des_iter(struct device *dev, void *data)
+{
+	device_unregister(dev);
+
+	return 0;
+}
+
+static void scmi_telemetry_dev_unregister(struct device *parent)
+{
+	device_for_each_child(parent, NULL, scmi_des_iter);
+	device_unregister(parent);
+}
+
+static ssize_t grp_obj_store(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+	struct scmi_tlm_setup *tsp = gde->tsp;
+	bool enabled, is_ena_entry;
+	int ret;
+
+	ret = kstrtobool(buf, &enabled);
+	if (ret)
+		return ret;
+
+	is_ena_entry = !strncmp(attr->attr.name, "enable", 6);
+	ret = tsp->ops->state_set(tsp->ph, true, gde->grp->id,
+				  is_ena_entry ? &enabled : NULL,
+				  !is_ena_entry ? &enabled : NULL);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t grp_obj_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+	bool enabled, is_ena_entry;
+
+	is_ena_entry = !strncmp(attr->attr.name, "enable", 6);
+	enabled = is_ena_entry ? gde->grp->enabled : gde->grp->tstamp_enabled;
+
+	return sysfs_emit(buf, "%c\n", enabled ? 'Y' : 'N');
+}
+
+static struct device_attribute dev_attr_grp_enable = {
+	.attr	= { .name = "enable", .mode = 0600 },
+	.show	= grp_obj_show,
+	.store	= grp_obj_store,
+};
+
+static struct device_attribute dev_attr_grp_tstamp_enable = {
+	.attr	= { .name = "tstamp_enable", .mode = 0600 },
+	.show	= grp_obj_show,
+	.store	= grp_obj_store,
+};
+
+static ssize_t composing_des_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+
+	return sysfs_emit(buf, "%s\n", gde->grp->des_str);
+}
+static DEVICE_ATTR_RO(composing_des);
+
+static ssize_t grp_current_update_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+
+	return __current_update_show(buf,
+				     gde->grp->intervals.active_update_interval);
+}
+
+static ssize_t grp_current_update_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+	struct scmi_tlm_setup *tsp = gde->tsp;
+
+	return __current_update_store(tsp, buf, len, gde->grp->id);
+}
+
+static struct device_attribute dev_attr_grp_current_update = {
+	.attr	= { .name = "current_update_interval_ms", .mode = 0600 },
+	.show	= grp_current_update_show,
+	.store	= grp_current_update_store,
+};
+
+static ssize_t grp_intervals_discrete_show(struct device *dev,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+
+	return __intervals_discrete_show(buf, gde->grp->intervals.discrete);
+}
+
+static struct device_attribute dev_attr_grp_intervals_discrete = {
+	.attr	= { .name = "intervals_discrete", .mode = 0400 },
+	.show	= grp_intervals_discrete_show,
+};
+
+static ssize_t grp_available_intervals_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+
+	return __available_update_show(buf, &gde->grp->intervals);
+}
+
+static struct device_attribute dev_attr_grp_available_intervals = {
+	.attr	= { .name = "available_update_intervals_ms", .mode = 0400 },
+	.show	= grp_available_intervals_show,
+};
+
+static ssize_t grp_des_bulk_read_show(struct device *dev,
+				      struct device_attribute *attr, char *buf)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+	struct scmi_tlm_instance *ti =
+		groups_dev_to_tlm_instance(gde->dev.parent);
+
+	return __des_bulk_read_show(ti, gde->grp->id, buf, PAGE_SIZE);
+}
+
+static ssize_t grp_des_single_sample_read_show(struct device *dev,
+					       struct device_attribute *attr,
+					       char *buf)
+{
+	struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+	struct scmi_tlm_instance *ti =
+		groups_dev_to_tlm_instance(gde->dev.parent);
+
+	return __des_single_sample_read_show(ti, gde->grp->id, buf, PAGE_SIZE);
+}
+
+static struct device_attribute dev_attr_grp_des_bulk_read = {
+	.attr	= { .name = "des_bulk_read", .mode = 0400 },
+	.show	= grp_des_bulk_read_show,
+};
+
+static struct device_attribute dev_attr_grp_des_single_sample_read = {
+	.attr	= { .name = "des_single_sample_read", .mode = 0400 },
+	.show	= grp_des_single_sample_read_show,
+};
+
+static struct attribute *scmi_grp_attrs[] = {
+	&dev_attr_grp_enable.attr,
+	&dev_attr_grp_tstamp_enable.attr,
+	&dev_attr_grp_des_bulk_read.attr,
+	&dev_attr_grp_des_single_sample_read.attr,
+	&dev_attr_composing_des.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(scmi_grp);
+
+static int scmi_telemetry_groups_initialize(struct device *dev,
+					    struct scmi_tlm_instance *ti)
+{
+	int ret;
+
+	if (ti->info->num_groups == 0)
+		return 0;
+
+	ret = scmi_telemetry_dev_register(&ti->groups_dev, &ti->dev, "groups");
+	if (ret)
+		return ret;
+
+	for (int i = 0; i < ti->info->num_groups; i++) {
+		const struct scmi_telemetry_group *grp = &ti->info->des_groups[i];
+		struct scmi_tlm_grp_dev *gdev;
+		char name[16];
+
+		gdev = devm_kzalloc(dev, sizeof(*gdev), GFP_KERNEL);
+		if (!gdev) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		gdev->tsp = ti->tsp;
+		gdev->grp = grp;
+		gdev->dev.groups = scmi_grp_groups;
+
+		snprintf(name, 8, "%d", grp->id);
+		ret = scmi_telemetry_dev_register(&gdev->dev,
+						  &ti->groups_dev, name);
+		if (ret)
+			goto err;
+
+		if (ti->info->per_group_config_support) {
+			sysfs_add_file_to_group(&gdev->dev.kobj,
+						&dev_attr_grp_current_update.attr,
+						NULL);
+			sysfs_add_file_to_group(&gdev->dev.kobj,
+						&dev_attr_grp_intervals_discrete.attr,
+						NULL);
+			sysfs_add_file_to_group(&gdev->dev.kobj,
+						&dev_attr_grp_available_intervals.attr,
+						NULL);
+		}
+	}
+
+	dev_info(dev, "Found %d Telemetry GROUPS resources.\n",
+		 ti->info->num_groups);
+
+	return 0;
+
+err:
+	scmi_telemetry_dev_unregister(&ti->groups_dev);
+
+	return ret;
+}
+
+static int scmi_telemetry_des_initialize(struct device *dev,
+					 struct scmi_tlm_instance *ti)
+{
+	int ret;
+
+	ret = scmi_telemetry_dev_register(&ti->des_dev, &ti->dev, "des");
+	if (ret)
+		return ret;
+
+	for (int i = 0; i < ti->info->num_de; i++) {
+		const struct scmi_telemetry_de *de = ti->info->des[i];
+		struct scmi_tlm_de_dev *tdev;
+		char name[16];
+
+		tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
+		if (!tdev) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		tdev->tsp = ti->tsp;
+		tdev->de = de;
+		tdev->dev.groups = scmi_des_groups;
+
+		/*XXX What about of ID/name digits-length used ? */
+		snprintf(name, 8, "0x%04X", de->id);
+		ret = scmi_telemetry_dev_register(&tdev->dev,
+						  &ti->des_dev, name);
+		if (ret)
+			goto err;
+
+		if (de->name)
+			sysfs_add_file_to_group(&tdev->dev.kobj,
+						&dev_attr_name.attr, NULL);
+		if (de->tstamp_support) {
+			sysfs_add_file_to_group(&tdev->dev.kobj,
+						&dev_attr_tstamp_exp.attr,
+						NULL);
+			sysfs_add_file_to_group(&tdev->dev.kobj,
+						&dev_attr_tstamp_enable.attr,
+						NULL);
+		}
+	}
+
+	dev_info(dev, "Found %d Telemetry DE resources.\n",
+		 ti->info->num_de);
+
+	return 0;
+
+err:
+	scmi_telemetry_dev_unregister(&ti->des_dev);
+
+	return ret;
+}
+
+static int
+scmi_tlm_root_instance_initialize(struct device *dev,
+				  struct scmi_tlm_instance *ti, int instance_id)
+{
+	char name[16];
+	int ret;
+
+	ti->dev.class = &scmi_telemetry_class;
+	ti->dev.groups = scmi_telemetry_groups;
+
+	snprintf(name, 16, "scmi_tlm_%d", instance_id);
+	ret = scmi_telemetry_dev_register(&ti->dev, NULL, name);
+	if (ret)
+		return ret;
+
+	if (ti->info->reset_support)
+		ret = sysfs_add_file_to_group(&ti->dev.kobj,
+					      &dev_attr_reset.attr, NULL);
+
+	return ret;
+}
+
+static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp,
+					       int instance_id)
+{
+	const struct scmi_telemetry_proto_ops *tlm_ops = tsp->ops;
+	struct device *dev = &tsp->sdev->dev;
+	struct scmi_tlm_instance *ti;
+	int ret;
+
+	ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+	if (!ti)
+		return ERR_PTR(-ENOMEM);
+
+	ti->info = tlm_ops->info_get(tsp->ph);
+	if (!ti->info) {
+		dev_err(dev, "invalid Telemetry info !\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	ti->tsp = tsp;
+
+	ret = scmi_tlm_root_instance_initialize(dev, ti, instance_id);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = scmi_telemetry_des_initialize(dev, ti);
+	if (ret) {
+		device_unregister(&ti->dev);
+		return ERR_PTR(ret);
+	}
+
+	ret = scmi_telemetry_groups_initialize(dev, ti);
+	if (ret) {
+		scmi_telemetry_dev_unregister(&ti->des_dev);
+		device_unregister(&ti->dev);
+		return ERR_PTR(ret);
+	}
+
+	return ti;
+}
+
+static int scmi_telemetry_probe(struct scmi_device *sdev)
+{
+	const struct scmi_handle *handle = sdev->handle;
+	struct scmi_protocol_handle *ph;
+	struct device *dev = &sdev->dev;
+	struct scmi_tlm_instance *ti;
+	struct scmi_tlm_setup *tsp;
+	const void *ops;
+
+	if (!handle)
+		return -ENODEV;
+
+	ops = handle->devm_protocol_get(sdev, sdev->protocol_id, &ph);
+	if (IS_ERR(ops))
+		return dev_err_probe(dev, PTR_ERR(ops),
+				     "Cannot access protocol:0x%X\n",
+				     sdev->protocol_id);
+
+	tsp = devm_kzalloc(&sdev->dev, sizeof(*tsp), GFP_KERNEL);
+	if (!tsp)
+		return -ENOMEM;
+
+	tsp->sdev = sdev;
+	tsp->ops = ops;
+	tsp->ph = ph;
+
+	//TODO Better to get info->id from SCMI/core
+	ti = scmi_tlm_init(tsp, atomic_fetch_inc(&scmi_tlm_instance_count));
+	if (IS_ERR(ti))
+		return PTR_ERR(ti);
+
+	dev_set_drvdata(&sdev->dev, ti);
+
+	return 0;
+}
+
+static void scmi_telemetry_remove(struct scmi_device *sdev)
+{
+	struct device *dev = &sdev->dev;
+	struct scmi_tlm_instance *ti;
+	bool enabled = false;
+	int ret;
+
+	ti = dev_get_drvdata(&sdev->dev);
+
+	ret = ti->tsp->ops->collection_configure(ti->tsp->ph,
+						 SCMI_TLM_GRP_INVALID, true,
+						 &enabled, NULL, NULL);
+	if (ret)
+		dev_warn(dev, "Failed to stop Telemetry collection\n");
+
+	scmi_telemetry_dev_unregister(&ti->groups_dev);
+	scmi_telemetry_dev_unregister(&ti->des_dev);
+	device_unregister(&ti->dev);
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+	{ SCMI_PROTOCOL_TELEMETRY, "telemetry" },
+	{ },
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_telemetry_driver = {
+	.name = "scmi-telemetry-driver",
+	.probe = scmi_telemetry_probe,
+	.remove = scmi_telemetry_remove,
+	.id_table = scmi_id_table,
+};
+
+static int __init scmi_telemetry_init(void)
+{
+	int ret;
+
+	ret = class_register(&scmi_telemetry_class);
+	if (ret)
+		return ret;
+
+	ret = scmi_register(&scmi_telemetry_driver);
+	if (ret)
+		class_unregister(&scmi_telemetry_class);
+
+	return ret;
+}
+module_init(scmi_telemetry_init);
+
+static void __exit scmi_telemetry_exit(void)
+{
+	scmi_unregister(&scmi_telemetry_driver);
+
+	class_unregister(&scmi_telemetry_class);
+}
+module_exit(scmi_telemetry_exit);
+
+MODULE_AUTHOR("Cristian Marussi <cristian.marussi at arm.com>");
+MODULE_DESCRIPTION("ARM SCMI Telemetry Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.47.0




More information about the linux-arm-kernel mailing list