diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c
index acc96f0cc224..4fc8d48ef3be 100644
--- a/drivers/platform/x86/intel/pmt/class.c
+++ b/drivers/platform/x86/intel/pmt/class.c
@@ -17,7 +17,7 @@
 #include "../vsec.h"
 #include "class.h"
 
-#define PMT_XA_START		0
+#define PMT_XA_START		1
 #define PMT_XA_MAX		INT_MAX
 #define PMT_XA_LIMIT		XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
 #define GUID_SPR_PUNIT		0x9956f43f
@@ -160,12 +160,12 @@ static struct class intel_pmt_class = {
 };
 
 static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
-				    struct intel_pmt_header *header,
 				    struct intel_vsec_device *ivdev,
 				    struct resource *disc_res)
 {
 	struct pci_dev *pci_dev = ivdev->pcidev;
 	struct device *dev = &ivdev->auxdev.dev;
+	struct intel_pmt_header *header = &entry->header;
 	u8 bir;
 
 	/*
@@ -271,7 +271,7 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
 	if (ns->attr_grp) {
 		ret = sysfs_create_group(entry->kobj, ns->attr_grp);
 		if (ret)
-			goto fail_sysfs;
+			goto fail_sysfs_create_group;
 	}
 
 	/* if size is 0 assume no data buffer, so no file needed */
@@ -296,13 +296,23 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
 	entry->pmt_bin_attr.size = entry->size;
 
 	ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);
-	if (!ret)
-		return 0;
+	if (ret)
+		goto fail_ioremap;
 
+	if (ns->pmt_add_handle) {
+		ret = ns->pmt_add_handle(entry, parent);
+		if (ret)
+			goto fail_add_handle;
+	}
+
+	return 0;
+
+fail_add_handle:
+	sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
 fail_ioremap:
 	if (ns->attr_grp)
 		sysfs_remove_group(entry->kobj, ns->attr_grp);
-fail_sysfs:
+fail_sysfs_create_group:
 	device_unregister(dev);
 fail_dev_create:
 	xa_erase(ns->xa, entry->devid);
@@ -314,7 +324,6 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespa
 			 struct intel_vsec_device *intel_vsec_dev, int idx)
 {
 	struct device *dev = &intel_vsec_dev->auxdev.dev;
-	struct intel_pmt_header header;
 	struct resource	*disc_res;
 	int ret;
 
@@ -324,16 +333,15 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespa
 	if (IS_ERR(entry->disc_table))
 		return PTR_ERR(entry->disc_table);
 
-	ret = ns->pmt_header_decode(entry, &header, dev);
+	ret = ns->pmt_header_decode(entry, dev);
 	if (ret)
 		return ret;
 
-	ret = intel_pmt_populate_entry(entry, &header, intel_vsec_dev, disc_res);
+	ret = intel_pmt_populate_entry(entry, intel_vsec_dev, disc_res);
 	if (ret)
 		return ret;
 
 	return intel_pmt_dev_register(entry, ns, dev);
-
 }
 EXPORT_SYMBOL_NS_GPL(intel_pmt_dev_create, INTEL_PMT);
 
diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h
index 17bc40b77efc..7b730cdaed5a 100644
--- a/drivers/platform/x86/intel/pmt/class.h
+++ b/drivers/platform/x86/intel/pmt/class.h
@@ -8,6 +8,7 @@
 #include <linux/types.h>
 
 #include "../vsec.h"
+#include "telemetry.h"
 
 /* PMT access types */
 #define ACCESS_BARID		2
@@ -18,8 +19,27 @@
 #define GET_ADDRESS(v)		((v) & GENMASK(31, 3))
 
 struct xarray;
+struct pci_dev;
+
+struct telem_handle {
+	struct pci_dev		*parent;
+	struct telem_header	header;
+	struct device		*dev;
+	void __iomem		*base;
+	bool			present;
+	struct kref		kref;
+};
+
+struct intel_pmt_header {
+	u32	base_offset;
+	u32	size;
+	u32	guid;
+	u8	access_type;
+};
 
 struct intel_pmt_entry {
+	struct telem_handle	*handle;
+	struct intel_pmt_header	header;
 	struct bin_attribute	pmt_bin_attr;
 	struct kobject		*kobj;
 	void __iomem		*disc_table;
@@ -30,20 +50,14 @@ struct intel_pmt_entry {
 	int			devid;
 };
 
-struct intel_pmt_header {
-	u32	base_offset;
-	u32	size;
-	u32	guid;
-	u8	access_type;
-};
-
 struct intel_pmt_namespace {
 	const char *name;
 	struct xarray *xa;
 	const struct attribute_group *attr_grp;
 	int (*pmt_header_decode)(struct intel_pmt_entry *entry,
-				 struct intel_pmt_header *header,
 				 struct device *dev);
+	int (*pmt_add_handle)(struct intel_pmt_entry *entry,
+			      struct device *dev);
 };
 
 bool intel_pmt_is_early_client_hw(struct device *dev);
diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c
index bbb3d61d09f4..4014c02cafdb 100644
--- a/drivers/platform/x86/intel/pmt/crashlog.c
+++ b/drivers/platform/x86/intel/pmt/crashlog.c
@@ -223,10 +223,10 @@ static const struct attribute_group pmt_crashlog_group = {
 };
 
 static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
-				      struct intel_pmt_header *header,
 				      struct device *dev)
 {
 	void __iomem *disc_table = entry->disc_table;
+	struct intel_pmt_header *header = &entry->header;
 	struct crashlog_entry *crashlog;
 
 	if (!pmt_crashlog_supported(entry))
diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c
index 71cdef42084a..39ec8d9fb5b1 100644
--- a/drivers/platform/x86/intel/pmt/telemetry.c
+++ b/drivers/platform/x86/intel/pmt/telemetry.c
@@ -30,6 +30,14 @@
 /* Used by client hardware to identify a fixed telemetry entry*/
 #define TELEM_CLIENT_FIXED_BLOCK_GUID	0x10000000
 
+#define NUM_BYTES_QWORD(v)	((v) << 3)
+#define SAMPLE_ID_OFFSET(v)	((v) << 3)
+
+#define NUM_BYTES_DWORD(v)	((v) << 2)
+#define SAMPLE_ID_OFFSET32(v)	((v) << 2)
+
+static DEFINE_MUTEX(entry_lock);
+
 enum telem_type {
 	TELEM_TYPE_PUNIT = 0,
 	TELEM_TYPE_CRASHLOG,
@@ -58,10 +66,10 @@ static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
 }
 
 static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
-				   struct intel_pmt_header *header,
 				   struct device *dev)
 {
 	void __iomem *disc_table = entry->disc_table;
+	struct intel_pmt_header *header = &entry->header;
 
 	if (pmt_telem_region_overlaps(entry, dev))
 		return 1;
@@ -84,21 +92,199 @@ static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
 	return 0;
 }
 
+static int pmt_telem_add_handle(struct intel_pmt_entry *entry,
+				struct device *dev)
+{
+	struct telem_handle *handle;
+
+	entry->handle = kzalloc(sizeof(*(entry->handle)), GFP_KERNEL);
+	if (!entry->handle)
+		return -ENOMEM;
+
+	handle = entry->handle;
+	handle->dev = dev;
+	handle->parent = to_pci_dev(dev->parent);
+	handle->header.access_type = entry->header.access_type;
+	handle->header.guid = entry->header.guid;
+	handle->header.base_offset = entry->header.base_offset;
+	handle->header.size = entry->header.size;
+	handle->base = entry->base;
+	handle->present = true;
+
+	kref_init(&handle->kref);
+
+	return 0;
+}
+
 static DEFINE_XARRAY_ALLOC(telem_array);
 static struct intel_pmt_namespace pmt_telem_ns = {
 	.name = "telem",
 	.xa = &telem_array,
 	.pmt_header_decode = pmt_telem_header_decode,
+	.pmt_add_handle = pmt_telem_add_handle,
 };
 
+static void pmt_telem_handle_release(struct kref *kref)
+{
+	struct telem_handle *handle;
+
+	handle = container_of(kref, struct telem_handle, kref);
+	kfree(handle);
+}
+
+/*
+ * driver api
+ */
+int pmt_telem_get_next_entry(int start)
+{
+	struct intel_pmt_entry *entry;
+	unsigned long found_idx;
+
+	mutex_lock(&entry_lock);
+	xa_for_each_start(&telem_array, found_idx, entry, start) {
+		/*
+		 * Return first found index after start.
+		 * 0 is not valid id.
+		 */
+		if (found_idx > start)
+			break;
+	}
+	mutex_unlock(&entry_lock);
+
+	return found_idx == start ? 0 : found_idx;
+}
+EXPORT_SYMBOL_NS_GPL(pmt_telem_get_next_entry, INTEL_PMT);
+
+struct telem_handle *pmt_telem_register_handle(int devid)
+{
+	struct intel_pmt_entry *entry;
+	unsigned long index = devid;
+
+	mutex_lock(&entry_lock);
+	entry = xa_find(&telem_array, &index, index, XA_PRESENT);
+	if (!entry) {
+		mutex_unlock(&entry_lock);
+		return ERR_PTR(-ENXIO);
+	}
+
+	kref_get(&entry->handle->kref);
+	mutex_unlock(&entry_lock);
+
+	return entry->handle;
+}
+EXPORT_SYMBOL_NS_GPL(pmt_telem_register_handle, INTEL_PMT);
+
+void pmt_telem_unregister_handle(struct telem_handle *handle)
+{
+	kref_put(&handle->kref, pmt_telem_handle_release);
+}
+EXPORT_SYMBOL_NS_GPL(pmt_telem_unregister_handle, INTEL_PMT);
+
+int pmt_telem_get_devid_info(int devid, struct telem_entry_info *info)
+{
+	struct intel_pmt_entry *entry;
+	unsigned long index = devid;
+	int err = 0;
+
+	if (!info)
+		return -EINVAL;
+
+	mutex_lock(&entry_lock);
+	entry = xa_find(&telem_array, &index, index, XA_PRESENT);
+	if (!entry) {
+		err = -ENXIO;
+		goto unlock;
+	}
+
+	info->pdev = entry->handle->parent;
+	info->header = entry->handle->header;
+
+unlock:
+	mutex_unlock(&entry_lock);
+	return err;
+
+}
+EXPORT_SYMBOL_NS_GPL(pmt_telem_get_devid_info, INTEL_PMT);
+
+int
+pmt_telem_read(struct telem_handle *handle, u32 id, u64 *data, u32 count)
+{
+	u32 offset, size;
+
+	if (!handle->present)
+		return -ENODEV;
+
+	offset = SAMPLE_ID_OFFSET(id);
+	size = handle->header.size;
+
+	if ((offset + NUM_BYTES_QWORD(count)) > size)
+		return -EINVAL;
+
+	memcpy_fromio(data, handle->base + offset, NUM_BYTES_QWORD(count));
+
+	return handle->present ? 0 : -EPIPE;
+}
+EXPORT_SYMBOL_NS_GPL(pmt_telem_read, INTEL_PMT);
+
+int
+pmt_telem_read32(struct telem_handle *handle, u32 id, u32 *data, u32 count)
+{
+	u32 offset, size;
+
+	if (!handle->present)
+		return -ENODEV;
+
+	offset = SAMPLE_ID_OFFSET32(id);
+	size = handle->header.size;
+
+	if ((offset + NUM_BYTES_DWORD(count)) > size)
+		return -EINVAL;
+
+	memcpy_fromio(data, handle->base + offset, NUM_BYTES_DWORD(count));
+
+	return handle->present ? 0 : -EPIPE;
+}
+EXPORT_SYMBOL_NS_GPL(pmt_telem_read32, INTEL_PMT);
+
+struct telem_handle *
+pmt_telem_find_and_register_handle(struct pci_dev *pcidev, u32 guid, u16 pos)
+{
+	int devid = 0;
+	int inst = 0;
+	int err = 0;
+
+	while ((devid = pmt_telem_get_next_entry(devid))) {
+		struct telem_entry_info info;
+
+		err = pmt_telem_get_devid_info(devid, &info);
+		if (err)
+			return ERR_PTR(err);
+
+		if (info.header.guid == guid && info.pdev == pcidev) {
+			if (inst == pos)
+				return pmt_telem_register_handle(devid);
+			++inst;
+		}
+	}
+
+	return ERR_PTR(-ENXIO);
+}
+EXPORT_SYMBOL_NS(pmt_telem_find_and_register_handle, INTEL_PMT);
+
 static void pmt_telem_remove(struct auxiliary_device *auxdev)
 {
 	struct pmt_telem_priv *priv = auxiliary_get_drvdata(auxdev);
 	int i;
 
-	for (i = 0; i < priv->num_entries; i++)
-		intel_pmt_dev_destroy(&priv->entry[i], &pmt_telem_ns);
-}
+	mutex_lock(&entry_lock);
+	for (i = 0; i < priv->num_entries; i++) {
+		struct intel_pmt_entry *entry = &priv->entry[i];
+
+		kref_put(&entry->handle->kref, pmt_telem_handle_release);
+		intel_pmt_dev_destroy(entry, &pmt_telem_ns);
+	}
+	mutex_unlock(&entry_lock);
+};
 
 static int pmt_telem_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
 {
@@ -117,7 +303,9 @@ static int pmt_telem_probe(struct auxiliary_device *auxdev, const struct auxilia
 	for (i = 0; i < intel_vsec_dev->num_resources; i++) {
 		struct intel_pmt_entry *entry = &priv->entry[priv->num_entries];
 
+		mutex_lock(&entry_lock);
 		ret = intel_pmt_dev_create(entry, &pmt_telem_ns, intel_vsec_dev, i);
+		mutex_unlock(&entry_lock);
 		if (ret < 0)
 			goto abort_probe;
 		if (ret)
diff --git a/drivers/platform/x86/intel/pmt/telemetry.h b/drivers/platform/x86/intel/pmt/telemetry.h
new file mode 100644
index 000000000000..c78184f08d2e
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/telemetry.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INTEL_PMT_TELEM_H
+#define _INTEL_PMT_TELEM_H
+
+#include <linux/pm_runtime.h>
+
+/* Telemetry types */
+#define PMT_TELEM_TELEMETRY	0
+#define PMT_TELEM_CRASHLOG	1
+
+struct telem_handle;
+
+struct telem_header {
+	u8	access_type;
+	u16	size;
+	u32	guid;
+	u32	base_offset;
+};
+
+struct telem_entry_info {
+	struct pci_dev		*pdev;
+	struct telem_header	header;
+};
+
+struct pci_dev;
+
+/**
+ * pmt_telem_get_next_devid() - Get next devid for a telemetry entry
+ * @start:  starting devid to look from
+ *
+ * Use in a while loop to retrieve the devid of all available telemetry
+ * entries.
+ *
+ * Return:
+ * * devid       - devid of the next present handle from start
+ * * 0           - when no more handles are present after start
+ */
+int pmt_telem_get_next_entry(int start);
+
+/**
+ * pmt_telem_get_devid_info() - Get telemetry info for a telemetry entry from
+ * its devid
+ * @devid:  devid of the telemetry entry
+ * @info:   Endpoint info structure to be populated
+ *
+ * Return:
+ * * 0           - Success
+ * * -ENXIO      - devid no found
+ * * -EINVAL     - info is NULL
+ */
+int pmt_telem_get_devid_info(int devid, struct telem_entry_info *info);
+
+/**
+ * pmt_telem_register_handle() - Register a telemetry handle
+ * @devid:  devid of the telemetry entry
+ *
+ * Increments the kref usage counter for the handle.
+ *
+ * Return:
+ * * handle      - On success returns pointer to the telemetry handle
+ * * -ENXIO      - devid not found
+ */
+struct telem_handle *pmt_telem_register_handle(int devid);
+
+/**
+ * pmt_telem_unregister_handle() - Unregister a telemetry handle
+ * @handle: handle to unregister
+ *
+ * Decrements the kref usage counter for the handle.
+ */
+void pmt_telem_unregister_handle(struct telem_handle *handle);
+
+/**
+ * pmt_telem_find_and_register_handle() - Find and get a handle to a telemetry
+ * entry
+ * @pdev:   PCI device of the telemetry provider
+ * @guid:   GUID of the telemetry space
+ * @pos:    Instance of the guid in case of multiple instances
+ *
+ * Return:
+ * * handle      - On success returns pointer to the telemetry handle
+ * * -ENXIO      - Telemetry provider not found
+ */
+struct telem_handle *pmt_telem_find_and_register_handle(struct pci_dev *pcidev,
+							u32 guid, u16 pos);
+
+/**
+ * pmt_telem_read() - Read telemetry sample(s) from handle
+ * @ep:     Telemetry handle
+ * @id:     Starting sample id
+ * @data:   Allocated qword buffer
+ * @count:  Number of consecutive samples/qwords to read
+ *
+ * Return:
+ * * 0           - Success
+ * * -ENODEV	 - Entry is not present
+ * * -EINVAL     - The offset is out bounds of the sample space
+ * * -EPIPE	 - The device was removed during the read. Data written
+ *		   but should be considered invalid
+ */
+int pmt_telem_read(struct telem_handle *handle, u32 id, u64 *data,
+		   u32 count);
+
+/**
+ * pmt_telem_read32() - Read dword samples from handle
+ * @ep:     Telemetry handle
+ * @id:     Starting sample id
+ * @data:   Allocated dword buffer
+ * @count:  Number of consecutive samples/dwords to read
+ *
+ * Return:
+ * * 0           - Success
+ * * -ENODEV	 - Entry is not present
+ * * -EINVAL     - The offset is out bounds of the sample space
+ * * -EPIPE	 - The device was removed during the read. Data written
+ *		   but should be considered invalid.
+ */
+int pmt_telem_read32(struct telem_handle *handle, u32 id, u32 *data,
+		     u32 count);
+#endif
