diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
index c58631200dd5..9ca32241b895 100644
--- a/Documentation/Doxyfile.in
+++ b/Documentation/Doxyfile.in
@@ -868,7 +868,8 @@ EXCLUDE_SYMBOLS        = libcamera::SignalBase \
                          libcamera::SlotArgs \
                          libcamera::SlotBase \
                          libcamera::SlotMember \
-                         libcamera::SlotStatic
+                         libcamera::SlotStatic \
+                         std::*
 
 # The EXAMPLE_PATH tag can be used to specify one or more files or directories
 # that contain example code fragments that are included (see the \include
diff --git a/include/libcamera/control_ids.h b/include/libcamera/control_ids.h
new file mode 100644
index 000000000000..d0e700da9844
--- /dev/null
+++ b/include/libcamera/control_ids.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * control_ids.h : Control ID list
+ */
+
+#ifndef __LIBCAMERA_CONTROL_IDS_H__
+#define __LIBCAMERA_CONTROL_IDS_H__
+
+#include <functional>
+
+namespace libcamera {
+
+enum ControlId {
+};
+
+} /* namespace libcamera */
+
+namespace std {
+
+template<>
+struct hash<libcamera::ControlId> {
+	using argument_type = libcamera::ControlId;
+	using result_type = std::size_t;
+
+	result_type operator()(const argument_type &key) const noexcept
+	{
+		return std::hash<std::underlying_type<argument_type>::type>()(key);
+	}
+};
+
+} /* namespace std */
+
+#endif // __LIBCAMERA_CONTROL_IDS_H__
diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
new file mode 100644
index 000000000000..ad2d49d522c5
--- /dev/null
+++ b/include/libcamera/controls.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * controls.h - Control handling
+ */
+
+#ifndef __LIBCAMERA_CONTROLS_H__
+#define __LIBCAMERA_CONTROLS_H__
+
+#include <stdint.h>
+#include <string>
+#include <unordered_map>
+
+#include <libcamera/control_ids.h>
+
+namespace libcamera {
+
+class Camera;
+
+enum ControlValueType {
+	ControlValueNone,
+	ControlValueBool,
+	ControlValueInteger,
+	ControlValueInteger64,
+};
+
+class ControlValue
+{
+public:
+	ControlValue();
+	ControlValue(bool value);
+	ControlValue(int value);
+	ControlValue(int64_t value);
+
+	ControlValueType type() const { return type_; };
+	bool isNone() const { return type_ == ControlValueNone; };
+
+	void set(bool value);
+	void set(int value);
+	void set(int64_t value);
+
+	bool getBool() const;
+	int getInt() const;
+	int64_t getInt64() const;
+
+	std::string toString() const;
+
+private:
+	ControlValueType type_;
+
+	union {
+		bool bool_;
+		int integer_;
+		int64_t integer64_;
+	};
+};
+
+struct ControlIdentifier {
+	ControlId id;
+	const char *name;
+	ControlValueType type;
+};
+
+class ControlInfo
+{
+public:
+	explicit ControlInfo(ControlId id, const ControlValue &min = 0,
+			     const ControlValue &max = 0);
+
+	ControlId id() const { return ident_->id; }
+	const char *name() const { return ident_->name; }
+	ControlValueType type() const { return ident_->type; }
+
+	const ControlValue &min() const { return min_; }
+	const ControlValue &max() const { return max_; }
+
+	std::string toString() const;
+
+private:
+	const struct ControlIdentifier *ident_;
+	ControlValue min_;
+	ControlValue max_;
+};
+
+bool operator==(const ControlInfo &lhs, const ControlInfo &rhs);
+bool operator==(const ControlId &lhs, const ControlInfo &rhs);
+bool operator==(const ControlInfo &lhs, const ControlId &rhs);
+static inline bool operator!=(const ControlInfo &lhs, const ControlInfo &rhs)
+{
+	return !(lhs == rhs);
+}
+static inline bool operator!=(const ControlId &lhs, const ControlInfo &rhs)
+{
+	return !(lhs == rhs);
+}
+static inline bool operator!=(const ControlInfo &lhs, const ControlId &rhs)
+{
+	return !(lhs == rhs);
+}
+
+class ControlList
+{
+private:
+	using ControlListMap = std::unordered_map<const ControlInfo *, ControlValue>;
+
+public:
+	ControlList(Camera *camera);
+
+	using iterator = ControlListMap::iterator;
+	using const_iterator = ControlListMap::const_iterator;
+
+	iterator begin() { return controls_.begin(); }
+	iterator end() { return controls_.end(); }
+	const_iterator begin() const { return controls_.begin(); }
+	const_iterator end() const { return controls_.end(); }
+
+	bool contains(const ControlInfo *info) const { return controls_.count(info); };
+	bool empty() const { return controls_.empty(); }
+	std::size_t size() const { return controls_.size(); }
+	void clear() { controls_.clear(); }
+
+	ControlValue &operator[](const ControlInfo *info) { return controls_[info]; }
+
+	void update(const ControlList &list);
+
+private:
+	Camera *camera_;
+	ControlListMap controls_;
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_CONTROLS_H__ */
diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
index 15484724df01..3067120a1598 100644
--- a/include/libcamera/meson.build
+++ b/include/libcamera/meson.build
@@ -2,6 +2,8 @@ libcamera_api = files([
     'buffer.h',
     'camera.h',
     'camera_manager.h',
+    'control_ids.h',
+    'controls.h',
     'event_dispatcher.h',
     'event_notifier.h',
     'geometry.h',
diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
new file mode 100644
index 000000000000..22db2b93eff2
--- /dev/null
+++ b/src/libcamera/controls.cpp
@@ -0,0 +1,428 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * controls.cpp - Control handling
+ */
+
+#include <libcamera/controls.h>
+
+#include <sstream>
+#include <string>
+
+#include "log.h"
+#include "utils.h"
+
+/**
+ * \file controls.h
+ * \brief Describes control framework and controls supported by a camera
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Controls)
+
+/**
+ * \enum ControlValueType
+ * Determines the type of value represented by a ControlValue
+ * \var ControlValueNone
+ * Identifies an unset control value
+ * \var ControlValueBool
+ * Identifies controls storing a boolean value
+ * \var ControlValueInteger
+ * Identifies controls storing an integer value
+ * \var ControlValueInteger64
+ * Identifies controls storing a 64-bit integer value
+ */
+
+/**
+ * \class ControlValue
+ * \brief Abstract type for values in a control
+ */
+
+/**
+ * \brief Construct an empty ControlValue.
+ */
+ControlValue::ControlValue()
+	: type_(ControlValueNone)
+{
+}
+
+/**
+ * \brief Construct a Boolean ControlValue
+ * \param[in] value Boolean value to store
+ */
+ControlValue::ControlValue(bool value)
+	: type_(ControlValueBool), bool_(value)
+{
+}
+
+/**
+ * \brief Construct an integer ControlValue
+ * \param[in] value Integer value to store
+ */
+ControlValue::ControlValue(int value)
+	: type_(ControlValueInteger), integer_(value)
+{
+}
+
+/**
+ * \brief Construct a 64 bit integer ControlValue
+ * \param[in] value String representation to store
+ */
+ControlValue::ControlValue(int64_t value)
+	: type_(ControlValueInteger64), integer64_(value)
+{
+}
+
+/**
+ * \fn ControlValue::type
+ * \brief Return the type of value represented by the object
+ */
+
+/**
+ * \fn ControlValue::isNone
+ * \brief Determine if the value is initialised
+ * \return True if the value type is ControlValueNone, false otherwise
+ */
+
+/**
+ * \brief Set the value with a boolean
+ * \param[in] value Boolean value to store
+ */
+void ControlValue::set(bool value)
+{
+	type_ = ControlValueBool;
+	bool_ = value;
+}
+
+/**
+ * \brief Set the value with an integer
+ * \param[in] value Integer value to store
+ */
+void ControlValue::set(int value)
+{
+	type_ = ControlValueInteger;
+	integer_ = value;
+}
+
+/**
+ * \brief Set the value with a 64 bit integer
+ * \param[in] value 64 bit integer value to store
+ */
+void ControlValue::set(int64_t value)
+{
+	type_ = ControlValueInteger64;
+	integer64_ = value;
+}
+
+/**
+ * \brief Get the boolean value.
+ *
+ * The value type must be Boolean.
+ */
+bool ControlValue::getBool() const
+{
+	ASSERT(type_ == ControlValueBool);
+
+	return bool_;
+}
+
+/**
+ * \brief Get the integer value.
+ *
+ * The value type must be Integer or Integer64
+ */
+int ControlValue::getInt() const
+{
+	ASSERT(type_ == ControlValueInteger || type_ == ControlValueInteger64);
+
+	return integer_;
+}
+
+/**
+ * \brief Get the 64 bit integer value.
+ *
+ * The value type must be Integer or Integer64
+ */
+int64_t ControlValue::getInt64() const
+{
+	ASSERT(type_ == ControlValueInteger || type_ == ControlValueInteger64);
+
+	return integer64_;
+}
+
+/**
+ * \brief Assemble and return a string describing the value
+ * \return A string describing the ControlValue
+ */
+std::string ControlValue::toString() const
+{
+	switch (type_) {
+	case ControlValueNone:
+		return "<None>";
+	case ControlValueBool:
+		return bool_ ? "True" : "False";
+	case ControlValueInteger:
+		return std::to_string(integer_);
+	case ControlValueInteger64:
+		return std::to_string(integer64_);
+	}
+
+	return "<ValueType Error>";
+}
+
+/**
+ * \enum ControlId
+ * Control Identifiers
+ */
+
+/**
+ * \struct ControlIdentifier
+ * \brief Describes a ControlId with control specific constant meta-data.
+ *
+ * Defines a Control with a unique ID, a name, and a type.
+ * This structure is used as static part of the autogenerated control
+ * definitions, which are generated from the ControlId documentation.
+ *
+ * \var ControlIdentifier::id
+ * The unique ID for a control
+ * \var ControlIdentifier::name
+ * The string representation of the control
+ * \var ControlIdentifier::type
+ * The ValueType required to represent the control value
+ */
+
+/*
+ * The controlTypes are automatically generated to produce a control_types.cpp
+ * output. This file is not for public use, and so no suitable header exists
+ * for this sole usage of the controlTypes reference. As such the extern is
+ * only defined here for use during the ControlInfo constructor and should not
+ * be referenced directly elsewhere.
+ */
+extern const std::unordered_map<ControlId, ControlIdentifier> controlTypes;
+
+/**
+ * \class ControlInfo
+ * \brief Describe the information and capabilities of a Control
+ *
+ * The ControlInfo represents control specific meta-data which is constant on a
+ * per camera basis. ControlInfo classes are constructed by pipeline handlers
+ * to expose the controls they support and the metadata needed to utilise those
+ * controls.
+ */
+
+/**
+ * \brief Construct a ControlInfo with minimum and maximum range parameters.
+ */
+ControlInfo::ControlInfo(ControlId id, const ControlValue &min,
+			 const ControlValue &max)
+	: min_(min), max_(max)
+{
+	auto iter = controlTypes.find(id);
+	if (iter == controlTypes.end()) {
+		LOG(Controls, Fatal) << "Attempt to create invalid ControlInfo";
+		return;
+	}
+
+	ident_ = &iter->second;
+}
+
+/**
+ * \fn ControlInfo::id()
+ * \brief Retrieve the ID of the control information descriptor
+ * \return the ControlId
+ */
+
+/**
+ * \fn ControlInfo::name()
+ * \brief Retrieve the string name of the control information descriptor
+ * \return A string name for the Control
+ */
+
+/**
+ * \fn ControlInfo::type()
+ * \brief Retrieve the ValueType of the control information descriptor
+ * \return The control type
+ */
+
+/**
+ * \fn ControlInfo::min()
+ * \brief Reports the minimum value of the control
+ * \return a COntrolValue with the minimum setting for the control
+ */
+
+/**
+ * \fn ControlInfo::max()
+ * \brief Reports the maximum value of the control
+ * \return a ControlValue with the maximum setting for the control
+ */
+
+/**
+ * \brief Provide a string representation of the ControlInfo
+ */
+std::string ControlInfo::toString() const
+{
+	std::stringstream ss;
+
+	ss << name() << "[" << min_.toString() << ".." << max_.toString() << "]";
+
+	return ss.str();
+}
+
+/**
+ * \brief Compare control information for equality
+ * \param[in] lhs Left-hand side control information
+ * \param[in] rhs Right-hand side control information
+ *
+ * Control information are compared based on their ID only, as a camera may not
+ * have two separate controls with the same ID.
+ *
+ * \return True if \a lhs and \a rhs are equal, false otherwise
+ */
+bool operator==(const ControlInfo &lhs, const ControlInfo &rhs)
+{
+	return lhs.id() == rhs.id();
+}
+
+/**
+ * \brief Compare control ID and information for equality
+ * \param[in] lhs Left-hand side control identifier
+ * \param[in] rhs Right-hand side control information
+ *
+ * Control information are compared based on their ID only, as a camera may not
+ * have two separate controls with the same ID.
+ *
+ * \return True if \a lhs and \a rhs are equal, false otherwise
+ */
+bool operator==(const ControlId &lhs, const ControlInfo &rhs)
+{
+	return lhs == rhs.id();
+}
+
+/**
+ * \brief Compare control information and ID for equality
+ * \param[in] lhs Left-hand side control information
+ * \param[in] rhs Right-hand side control identifier
+ *
+ * Control information are compared based on their ID only, as a camera may not
+ * have two separate controls with the same ID.
+ *
+ * \return True if \a lhs and \a rhs are equal, false otherwise
+ */
+bool operator==(const ControlInfo &lhs, const ControlId &rhs)
+{
+	return lhs.id() == rhs;
+}
+
+/**
+ * \class ControlList
+ * \brief Associates a list of ControlIds with their values for a Camera.
+ *
+ * A ControlList specifies a map of ControlIds and Values and associated
+ * validation against the ControlInfo for the related Camera device.
+ */
+
+/**
+ * \brief Construct a ControlList with a reference to the Camera it applies on
+ */
+ControlList::ControlList(Camera *camera)
+	: camera_(camera)
+{
+}
+
+/**
+ * \typedef ControlList::iterator
+ * \brief Iterator for the controls contained within the list.
+ */
+
+/**
+ * \typedef ControlList::const_iterator
+ * \brief Const iterator for the controls contained within the list.
+ */
+
+/**
+ * \fn iterator ControlList::begin()
+ * \brief Retrieve an iterator to the first Control in the list
+ * \return An iterator to the first Control in the list
+ */
+
+/**
+ * \fn iterator ControlList::end()
+ * \brief Retrieve an iterator to the next element after the last controls in
+ * the instance.
+ * \return An iterator to the element following the last control in the instance
+ */
+
+/**
+ * \fn const_iterator ControlList::begin() const
+ * \brief Retrieve a const_iterator to the first Control in the list
+ * \return A const_iterator to the first Control in the list
+ */
+
+/**
+ * \fn const_iterator ControlList::end() const
+ * \brief Retrieve a constant iterator pointing to an empty element after the
+ * \return A const iterator to the element following the last control in the
+ * instance
+ */
+
+/**
+ * \fn ControlList::contains(const ControlInfo *info) const
+ * \brief Check if the ist contains a control with the specified \a info
+ * \param[in] info The control info
+ * \return True if the list contains a matching control, false otherwise
+ */
+
+/**
+ * \fn ControlList::empty()
+ * \brief Identify if the list is empty
+ * \return True if the list does not contain any control, false otherwise
+ */
+
+/**
+ * \fn ControlList::size()
+ * \brief Retrieve the number of controls in the list
+ * \return The number of Control entries stored in the list
+ */
+
+/**
+ * \fn ControlList::clear()
+ * \brief Removes all controls from the list
+ */
+
+/**
+ * \fn ControlList::operator[](const ControlInfo *info)
+ * \brief Access or insert the control specified by \a info
+ * \param[in] info The control info
+ *
+ * This method returns a reference to the control identified by \a info,
+ * inserting it in the list if the info is not already present.
+ *
+ * \return A reference to the value of the control identified by \a info
+ */
+
+/**
+ * \brief Update all Control values with the value from the given \a list
+ * \param list The list of controls to update or append to this list
+ *
+ * Update all controls in the ControlList, by the values given by \a list
+ * If the list already contains a control of this ID then it will be overwritten
+ */
+void ControlList::update(const ControlList &list)
+{
+	if (list.camera_ != camera_) {
+		LOG(Controls, Error)
+			<< "ControlLists can not be translated between cameras";
+		return;
+	}
+
+	for (auto it : list) {
+		const ControlInfo *info = it.first;
+		const ControlValue &value = it.second;
+
+		controls_[info] = value;
+	}
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/gen-controls.awk b/src/libcamera/gen-controls.awk
new file mode 100755
index 000000000000..a91529b575db
--- /dev/null
+++ b/src/libcamera/gen-controls.awk
@@ -0,0 +1,106 @@
+#!/usr/bin/awk -f
+
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# Controls are documented using Doxygen in the main control.cpp source.
+#
+# Generate control tables directly from the documentation, creating enumerations
+# to support the IDs and static type information regarding each control.
+
+BEGIN {
+	id=0
+	input=ARGV[1]
+	mode=ARGV[2]
+	output=ARGV[3]
+}
+
+# Detect Doxygen style comment blocks and ignore other lines
+/^\/\*\*$/ { in_doxygen=1; first_line=1; next }
+// { if (!in_doxygen) next }
+
+# Entry point for the Control Documentation
+/ * \\enum ControlId$/ { in_controls=1; first_line=0; next }
+// { if (!in_controls) next }
+
+# Extract control information
+/ \* \\var/ { names[++id]=$3; first_line=0; next }
+/ \* ControlType:/ { types[id] = $3 }
+
+# End of comment blocks
+/^ \*\// { in_doxygen=0 }
+
+# Identify the end of controls
+/^ \* \\/ { if (first_line) exit }
+// { first_line=0 }
+
+################################
+# Support output file generation
+
+function basename(file) {
+	sub(".*/", "", file)
+	return file
+}
+
+function Header(file, description) {
+	print "/* SPDX-License-Identifier: LGPL-2.1-or-later */" > file
+	print "/*" > file
+	print " * Copyright (C) 2019, Google Inc." > file
+	print " *" > file
+	print " * " basename(file) " - " description > file
+	print " *" > file
+	print " * This file is autogenerated. Do not edit." > file
+	print " */" > file
+	print "" > file
+}
+
+function EnterNameSpace(file) {
+	print "namespace libcamera {" > file
+	print "" > file
+}
+
+function ExitNameSpace(file) {
+	print "" > file
+	print "} /* namespace libcamera */" > file
+}
+
+function GenerateHeader(file) {
+	Header(file, "Control ID list")
+
+	print "#ifndef __LIBCAMERA_CONTROL_IDS_H__" > file
+	print "#define __LIBCAMERA_CONTROL_IDS_H__" > file
+	print "" > file
+
+	EnterNameSpace(file)
+	print "enum ControlId {" > file
+	for (i=1; i <= id; ++i) {
+		printf "\t%s,\n", names[i] > file
+	}
+	print "};" > file
+	ExitNameSpace(file)
+
+	print "" > file
+	print "#endif // __LIBCAMERA_CONTROL_IDS_H__" > file
+}
+
+function GenerateTable(file) {
+	Header(file, "Control types")
+	print "#include <libcamera/controls.h>" > file
+	print "" > file
+
+	EnterNameSpace(file)
+
+	print "extern const std::unordered_map<ControlId, ControlIdentifier>" > file
+	print "controlTypes {" > file
+	for (i=1; i <= id; ++i) {
+		printf "\t{ %s, { %s, \"%s\", ControlValue%s } },\n", names[i], names[i], names[i], types[i] > file
+	}
+	print "};" > file
+	ExitNameSpace(file)
+}
+
+END {
+	if (mode == "--header")
+		GenerateHeader(output)
+	else
+		GenerateTable(output)
+}
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 985aa7e8ab0e..b1ee92735e41 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -3,6 +3,7 @@ libcamera_sources = files([
     'camera.cpp',
     'camera_manager.cpp',
     'camera_sensor.cpp',
+    'controls.cpp',
     'device_enumerator.cpp',
     'device_enumerator_sysfs.cpp',
     'event_dispatcher.cpp',
@@ -66,6 +67,16 @@ if libudev.found()
     ])
 endif
 
+gen_controls = files('gen-controls.awk')
+
+control_types_cpp = custom_target('control_types_cpp',
+                                  input : files('controls.cpp'),
+                                  output : 'control_types.cpp',
+                                  depend_files : gen_controls,
+                                  command : [gen_controls, '@INPUT@', '--code', '@OUTPUT@'])
+
+libcamera_sources += control_types_cpp
+
 libcamera_deps = [
     cc.find_library('dl'),
     libudev,
