diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index e9fb838af4df..5a4a72a06346 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -5,6 +5,7 @@
 obj-$(CONFIG_ARM_SCPI_PROTOCOL)	+= arm_scpi.o
 obj-$(CONFIG_ARM_SCPI_POWER_DOMAIN) += scpi_pm_domain.o
 obj-$(CONFIG_ARM_SDE_INTERFACE)	+= arm_sdei.o
+obj-$(CONFIG_HAVE_ARM_SMCCC)	+= arm_smccc_conduit.o
 obj-$(CONFIG_DMI)		+= dmi_scan.o
 obj-$(CONFIG_DMI_SYSFS)		+= dmi-sysfs.o
 obj-$(CONFIG_EDD)		+= edd.o
diff --git a/drivers/firmware/arm_smccc_conduit.c b/drivers/firmware/arm_smccc_conduit.c
new file mode 100644
index 000000000000..37b90e92a4b3
--- /dev/null
+++ b/drivers/firmware/arm_smccc_conduit.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020, Linaro Limited
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/device.h>
+#include <linux/of.h>
+
+static enum arm_smccc_conduit arm_smccc_1_0_conduit = SMCCC_CONDUIT_NONE;
+
+/* Helpers for nice trace when called outside a device instance */
+#define PRINT_INFO(dev, ...)				\
+	do {						\
+		if (dev)				\
+			dev_info(dev, __VA_ARGS__);	\
+		else					\
+			pr_info(__VA_ARGS__);		\
+	} while (0)
+
+#define PRINT_WARN(dev, ...)				\
+	do {						\
+		if (dev)				\
+			dev_warn(dev, __VA_ARGS__);	\
+		else					\
+			pr_warn(__VA_ARGS__);		\
+	} while (0)
+
+#define PRINT_ERROR(dev, ...)				\
+	do {						\
+		if (dev)				\
+			dev_err(dev, __VA_ARGS__);	\
+		else					\
+			pr_err(__VA_ARGS__);		\
+	} while (0)
+
+static const char *conduit_str(enum arm_smccc_conduit conduit)
+{
+	static const char hvc_str[] = "HVC";
+	static const char smc_str[] = "SMC";
+	static const char unknown[] = "unknown";
+
+	switch (conduit) {
+	case SMCCC_CONDUIT_HVC:
+		return hvc_str;
+	case SMCCC_CONDUIT_SMC:
+		return smc_str;
+	default:
+		return unknown;
+	}
+}
+
+static int set_conduit(struct device *dev, enum arm_smccc_conduit conduit)
+{
+	switch (conduit) {
+	case SMCCC_CONDUIT_HVC:
+	case SMCCC_CONDUIT_SMC:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (arm_smccc_1_0_conduit == SMCCC_CONDUIT_NONE) {
+		arm_smccc_1_0_conduit = conduit;
+		return 0;
+	}
+
+	if (conduit == arm_smccc_1_0_conduit)
+		return 0;
+
+	PRINT_ERROR(dev, "inconsistent conduits %u (%s) vs %u (%s)\n",
+		    conduit, conduit_str(conduit),
+		    arm_smccc_1_0_conduit, conduit_str(arm_smccc_1_0_conduit));
+
+	return -EINVAL;
+}
+
+static enum arm_smccc_conduit method_to_conduit(const char *method)
+{
+	if (!strcmp("hvc", method))
+		return SMCCC_CONDUIT_HVC;
+	else if (!strcmp("smc", method))
+		return SMCCC_CONDUIT_SMC;
+	else
+		return SMCCC_CONDUIT_NONE;
+}
+
+static int set_conduit_from_node(struct device *dev, struct device_node *np)
+{
+	const char *method;
+
+	PRINT_INFO(dev, "probing for conduit method from DT.\n");
+
+	if (!np)
+		return -EINVAL;
+
+	if (!of_property_read_string(np, "method", &method)) {
+		enum arm_smccc_conduit dev_conduit = method_to_conduit(method);
+
+		if (dev_conduit == SMCCC_CONDUIT_NONE) {
+			PRINT_WARN(dev, "invalid \"method\" property \"%s\"\n",
+				   method);
+			return -EINVAL;
+		}
+
+		return set_conduit(dev, dev_conduit);
+	}
+
+	if (arm_smccc_1_0_conduit != SMCCC_CONDUIT_NONE)
+		return 0;
+
+	PRINT_WARN(dev, "missing \"method\" property\n");
+	return -ENXIO;
+}
+
+int devm_arm_smccc_1_0_set_conduit(struct device *dev)
+{
+	if (!dev || !dev->of_node)
+		return -EINVAL;
+
+	return set_conduit_from_node(dev, dev->of_node);
+}
+EXPORT_SYMBOL_GPL(devm_arm_smccc_1_0_set_conduit);
+
+int of_arm_smccc_1_0_set_conduit(struct device_node *np)
+{
+	if (!np)
+		return -EINVAL;
+
+	return set_conduit_from_node(NULL, np);
+}
+EXPORT_SYMBOL_GPL(of_arm_smccc_1_0_set_conduit);
+
+int arm_smccc_1_0_set_conduit(enum arm_smccc_conduit conduit)
+{
+	if (set_conduit(NULL, conduit))
+		return -EINVAL;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(arm_smccc_1_0_set_conduit);
+
+enum arm_smccc_conduit arm_smccc_1_0_get_conduit(void)
+{
+	return arm_smccc_1_0_conduit;
+}
+EXPORT_SYMBOL_GPL(arm_smccc_1_0_get_conduit);
diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h
index 59494df0f55b..4f5f17b00a6b 100644
--- a/include/linux/arm-smccc.h
+++ b/include/linux/arm-smccc.h
@@ -377,5 +377,111 @@ asmlinkage void __arm_smccc_hvc(unsigned long a0, unsigned long a1,
 			   ARM_SMCCC_OWNER_STANDARD_HYP,	\
 			   0x21)
 
+/* SMCCC v1.0 compliant invocation helpers */
+
+/*
+ * Like arm_smccc_1_0* but always returns SMCCC_RET_NOT_SUPPORTED.
+ * Used when the SMCCC conduit is not defined. The empty asm statement
+ * avoids compiler warnings about unused variables.
+ */
+#define __fail_smccc_1_0(...)						\
+	do {								\
+		__declare_args(7, __VA_ARGS__);				\
+		asm ("" __constraints(7));				\
+		___res->a0 = SMCCC_RET_NOT_SUPPORTED;			\
+	} while (0)
+
+/*
+ * arm_smccc_1_0_invoke() - make an SMCCC v1.0 compliant call
+ *
+ * This is a macro taking eight source arguments and an return structure.
+ * It uses the SMCCC conduit registered during driver(s) initialization.
+ *
+ * @a0-a7: arguments passed in registers 0 to 7
+ * @res: result values from registers 0 to 3
+ *
+ * This macro will make either an HVC call or an SMC call depending on the
+ * specified SMCCC conduit. If no valid conduit is available then -1
+ * (SMCCC_RET_NOT_SUPPORTED) is returned in @res.a0.
+ *
+ * The return value provides the conduit that was used.
+ */
+#define arm_smccc_1_0_invoke(...) ({					\
+		enum arm_smccc_conduit conduit = arm_smccc_1_0_get_conduit(); \
+		switch (conduit) {					\
+		case SMCCC_CONDUIT_HVC:					\
+			arm_smccc_hvc(__VA_ARGS__);			\
+			break;						\
+		case SMCCC_CONDUIT_SMC:					\
+			arm_smccc_smc(__VA_ARGS__);			\
+			break;						\
+		default:						\
+			__fail_smccc_1_0(__VA_ARGS__);			\
+			conduit = SMCCC_CONDUIT_NONE;			\
+		}							\
+		conduit;						\
+	})
+
+struct device;
+struct device_node;
+
+#ifdef CONFIG_HAVE_ARM_SMCCC
+/**
+ * arm_smccc_1_0_get_conduit() - Return registered SMCCC conduit
+ */
+enum arm_smccc_conduit arm_smccc_1_0_get_conduit(void);
+
+/**
+ * arm_smccc_1_0_set_conduit - Register SMCCC invocation conduit
+ * @conduit: conduit to register
+ *
+ * Return 0 on success and -EINVAL on failure.
+ */
+int arm_smccc_1_0_set_conduit(enum arm_smccc_conduit);
+
+/**
+ * devm_arm_smccc_1_0_set_conduit() - Set SMCCC v1.0 conduit if found in device
+ * @dev: Device instance
+ *
+ * Set the SMCCC invocation conduit based on device node if it has a "methhod"
+ * property that defines the SMCCC conduit to be used. If it has not, check a
+ * conduit is already registered.
+ *
+ * Return 0 on success, -ENXIO if no conduit found, -EINVAL otherwise.
+ */
+int devm_arm_smccc_1_0_set_conduit(struct device *dev);
+
+/**
+ * of_arm_smccc_1_0_set_conduit() - Set SMCCC v1.0 conduit if found in FDT node
+ * @np: Node instance
+ *
+ * Set the SMCCC invocation conduit based on device node if it has a "methhod"
+ * property that defines the SMCCC conduit to be used. If it has not, check a
+ * conduit is already registered.
+ *
+ * Return 0 on success, -ENXIO if no conduit found, -EINVAL otherwise.
+ */
+int of_arm_smccc_1_0_set_conduit(struct device_node *np);
+#else
+static inline enum arm_smccc_conduit arm_smccc_1_0_get_conduit(void)
+{
+	return SMCCC_CONDUIT_NONE;
+}
+
+static inline int arm_smccc_1_0_set_conduit(enum arm_smccc_conduit);
+{
+	return -EINVAL;
+}
+
+static inline int devm_arm_smccc_1_0_set_conduit(struct device *dev);
+{
+	return -EINVAL;
+}
+
+static inline int of_arm_smccc_1_0_set_conduit(struct device_node *np);
+{
+	return -EINVAL;
+}
+#endif /* CONFIG_HAVE_ARM_SMCCC */
 #endif /*__ASSEMBLY__*/
 #endif /*__LINUX_ARM_SMCCC_H*/
