diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
index 7c2bb2872..407a12256 100644
--- a/include/libcamera/controls.h
+++ b/include/libcamera/controls.h
@@ -8,6 +8,7 @@
 #pragma once
 
 #include <assert.h>
+#include <map>
 #include <optional>
 #include <set>
 #include <stdint.h>
@@ -213,14 +214,14 @@ private:
 class ControlId
 {
 public:
-	ControlId(unsigned int id, const std::string &name, ControlType type)
-		: id_(id), name_(name), type_(type)
-	{
-	}
+	ControlId(unsigned int id, const std::string &name, ControlType type,
+		  const std::map<std::string, int32_t> &nameValueMap = {});
 
 	unsigned int id() const { return id_; }
 	const std::string &name() const { return name_; }
 	ControlType type() const { return type_; }
+	const std::map<std::string, int32_t> &nameValueMap() const { return nameValueMap_; }
+	const std::string enumName(int32_t value) const;
 
 private:
 	LIBCAMERA_DISABLE_COPY_AND_MOVE(ControlId)
@@ -228,6 +229,8 @@ private:
 	unsigned int id_;
 	std::string name_;
 	ControlType type_;
+	std::map<std::string, int32_t> nameValueMap_;
+	std::map<int32_t, std::string> reverseMap_;
 };
 
 static inline bool operator==(unsigned int lhs, const ControlId &rhs)
@@ -256,8 +259,8 @@ class Control : public ControlId
 public:
 	using type = T;
 
-	Control(unsigned int id, const char *name)
-		: ControlId(id, name, details::control_type<std::remove_cv_t<T>>::value)
+	Control(unsigned int id, const char *name, const std::map<std::string, int32_t> &nameValueMap = {})
+		: ControlId(id, name, details::control_type<std::remove_cv_t<T>>::value, nameValueMap)
 	{
 	}
 
diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in
index 05c8fb385..3a2049311 100644
--- a/src/libcamera/control_ids.cpp.in
+++ b/src/libcamera/control_ids.cpp.in
@@ -89,8 +89,10 @@ extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap = {
 	{ "{{enum.name}}", {{enum.name}} },
 {%- endfor %}
 };
-{% endif -%}
+extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}", {{ctrl.name}}NameValueMap);
+{% else -%}
 extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}");
+{% endif -%}
 {%- endfor %}
 
 {% if vendor != 'libcamera' %}
diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
index dba744042..1c7e7fde1 100644
--- a/src/libcamera/controls.cpp
+++ b/src/libcamera/controls.cpp
@@ -7,6 +7,8 @@
 
 #include <libcamera/controls.h>
 
+#include <algorithm>
+#include <map>
 #include <sstream>
 #include <string.h>
 #include <string>
@@ -389,7 +391,15 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
  * \param[in] id The control numerical ID
  * \param[in] name The control name
  * \param[in] type The control data type
+ * \param[in] nameValueMap The map from enum names to values (optional)
  */
+ControlId::ControlId(unsigned int id, const std::string &name, ControlType type,
+		     const std::map<std::string, int32_t> &nameValueMap)
+	: id_(id), name_(name), type_(type), nameValueMap_(nameValueMap)
+{
+	for (const auto &pair : nameValueMap_)
+		reverseMap_[pair.second] = pair.first;
+}
 
 /**
  * \fn unsigned int ControlId::id() const
@@ -409,6 +419,24 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
  * \return The control data type
  */
 
+/**
+ * \brief Retrieve the name of an enum value
+ * \return The name of the enum value
+ */
+const std::string ControlId::enumName(int32_t value) const
+{
+	if (reverseMap_.find(value) != reverseMap_.end())
+		return reverseMap_.at(value);
+
+	return "UNKNOWN";
+}
+
+/**
+ * \fn std::map<std::string, int32_t> ControlId::nameValueMap() const
+ * \brief Retrieve the map from enum names to enum values (if applicable)
+ * \return The map from enum names to enum values
+ */
+
 /**
  * \fn bool operator==(unsigned int lhs, const ControlId &rhs)
  * \brief Compare a ControlId with a control numerical ID
@@ -459,6 +487,7 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
  * \brief Construct a Control instance
  * \param[in] id The control numerical ID
  * \param[in] name The control name
+ * \param[in] nameValueMap The map from enum names to values (optional)
  *
  * The control data type is automatically deduced from the template type T.
  */
