diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
index 458b84e..ac74122 100644
--- a/include/libcamera/controls.h
+++ b/include/libcamera/controls.h
@@ -20,6 +20,8 @@ enum ControlType {
 	ControlTypeBool,
 	ControlTypeInteger32,
 	ControlTypeInteger64,
+	ControlTypeString,
+	ControlTypeDouble,
 };
 
 class ControlValue
@@ -29,6 +31,8 @@ public:
 	ControlValue(bool value);
 	ControlValue(int32_t value);
 	ControlValue(int64_t value);
+	ControlValue(std::string value);
+	ControlValue(double value);
 
 	ControlType type() const { return type_; }
 	bool isNone() const { return type_ == ControlTypeNone; }
@@ -53,7 +57,9 @@ private:
 		bool bool_;
 		int32_t integer32_;
 		int64_t integer64_;
+		double double_;
 	};
+	std::string string_;
 };
 
 class ControlId
diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
index 0031cd0..6eb79c9 100644
--- a/src/libcamera/controls.cpp
+++ b/src/libcamera/controls.cpp
@@ -58,6 +58,10 @@ LOG_DEFINE_CATEGORY(Controls)
  * The control stores a 32-bit integer value
  * \var ControlTypeInteger64
  * The control stores a 64-bit integer value
+ * \var ControlTypeString
+ * The control stores a std::string value
+ * \var ControlTypeDouble
+ * The control stores a double value
  */
 
 /**
@@ -100,6 +104,24 @@ ControlValue::ControlValue(int64_t value)
 {
 }
 
+/**
+ * \brief Construct a std::string ControlValue
+ * \param[in] value String value to store
+ */
+ControlValue::ControlValue(std::string value)
+	: type_(ControlTypeString), string_(value)
+{
+}
+
+/**
+ * \brief Construct a double ControlValue
+ * \param[in] value Double value to store
+ */
+ControlValue::ControlValue(double value)
+	: type_(ControlTypeDouble), double_(value)
+{
+}
+
 /**
  * \fn ControlValue::type()
  * \brief Retrieve the data type of the value
@@ -153,6 +175,18 @@ const int64_t &ControlValue::get<int64_t>() const
 	return integer64_;
 }
 
+template<>
+const std::string &ControlValue::get<std::string>() const
+{
+	return string_;
+}
+
+template<>
+const double &ControlValue::get<double>() const
+{
+	return double_;
+}
+
 template<>
 void ControlValue::set<bool>(const bool &value)
 {
@@ -173,6 +207,21 @@ void ControlValue::set<int64_t>(const int64_t &value)
 	type_ = ControlTypeInteger64;
 	integer64_ = value;
 }
+
+template<>
+void ControlValue::set<std::string>(const std::string &value)
+{
+	type_ = ControlTypeString;
+	string_ = value;
+}
+
+template<>
+void ControlValue::set<double>(const double &value)
+{
+	type_ = ControlTypeDouble;
+	double_ = value;
+}
+
 #endif /* __DOXYGEN__ */
 
 /**
@@ -190,6 +239,10 @@ std::string ControlValue::toString() const
 		return std::to_string(integer32_);
 	case ControlTypeInteger64:
 		return std::to_string(integer64_);
+	case ControlTypeString:
+		return string_;
+	case ControlTypeDouble:
+		return std::to_string(double_);
 	}
 
 	return "<ValueType Error>";
@@ -211,6 +264,10 @@ bool ControlValue::operator==(const ControlValue &other) const
 		return integer32_ == other.integer32_;
 	case ControlTypeInteger64:
 		return integer64_ == other.integer64_;
+	case ControlTypeString:
+		return string_ == other.string_;
+	case ControlTypeDouble:
+		return double_ == other.double_;
 	default:
 		return false;
 	}
@@ -341,6 +398,18 @@ Control<int64_t>::Control(unsigned int id, const char *name)
 	: ControlId(id, name, ControlTypeInteger64)
 {
 }
+
+template<>
+Control<std::string>::Control(unsigned int id, const char *name)
+	: ControlId(id, name, ControlTypeString)
+{
+}
+
+template<>
+Control<double>::Control(unsigned int id, const char *name)
+	: ControlId(id, name, ControlTypeDouble)
+{
+}
 #endif /* __DOXYGEN__ */
 
 /**
@@ -583,7 +652,8 @@ void ControlInfoMap::generateIdmap()
 	idmap_.clear();
 
 	for (const auto &ctrl : *this) {
-		if (ctrl.first->type() != ctrl.second.min().type()) {
+		if (ctrl.first->type() != ControlTypeString &&
+			(ctrl.first->type() != ctrl.second.min().type())) {
 			LOG(Controls, Error)
 				<< "Control " << utils::hex(ctrl.first->id())
 				<< " type and range type mismatch";
diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
index a1ffa84..55174da 100644
--- a/test/controls/control_value.cpp
+++ b/test/controls/control_value.cpp
@@ -21,10 +21,14 @@ protected:
 	{
 		ControlValue integer(1234);
 		ControlValue boolean(true);
+		ControlValue fractional(3.142);
+		ControlValue string(std::string("MyString"));
 
 		/* Just a string conversion output test... no validation */
 		cout << "Int: " << integer.toString()
 		     << " Bool: " << boolean.toString()
+		     << " Frac: " << fractional.toString()
+		     << " String: " << string.toString()
 		     << endl;
 
 		if (integer.get<int32_t>() != 1234) {
@@ -37,6 +41,16 @@ protected:
 			return TestFail;
 		}
 
+		if (fractional.get<double>() != 3.142) {
+			cerr << "Failed to get Fractional" << endl;
+			return TestFail;
+		}
+
+		if (string.get<std::string>() != std::string("MyString")) {
+			cerr << "Failed to get String" << endl;
+			return TestFail;
+		}
+
 		/* Test an uninitialised value, and updating it. */
 
 		ControlValue value;
@@ -62,6 +76,18 @@ protected:
 			return TestFail;
 		}
 
+		value.set<double>(2.718);
+		if (value.get<double>() != 2.718) {
+			cerr << "Failed to get Fractional" << endl;
+			return TestFail;
+		}
+
+		value.set<std::string>(std::string("Test"));
+		if (value.get<std::string>() != std::string("Test")) {
+			cerr << "Failed to get String" << endl;
+			return TestFail;
+		}
+
 		return TestPass;
 	}
 };
