diff --git a/src/cam/options.cpp b/src/cam/options.cpp
index d3bff1cd897a5cfb..f63fd3b495e29986 100644
--- a/src/cam/options.cpp
+++ b/src/cam/options.cpp
@@ -30,11 +30,22 @@ bool OptionsParser::addOption(int opt, const char *help, const char *name,
 	if (optionsMap_.find(opt) != optionsMap_.end())
 		return false;
 
-	optionsMap_[opt] = Option({ opt, name, argument, argumentName, help });
+	optionsMap_[opt] = Option({ opt, name, argument, argumentName, help,
+				    nullptr });
 
 	return true;
 }
 
+void OptionsParser::addOption(int opt, const char *help, KeyValueParser &parser,
+			      const char *name)
+{
+	if (!addOption(opt, help, name, ArgumentRequired,
+		       "key=value[,key=value,...]"))
+		return;
+
+	optionsMap_[opt].keyValueParser = &parser;
+}
+
 OptionsParser::Options OptionsParser::parse(int argc, char **argv)
 {
 	OptionsParser::Options options;
@@ -103,6 +114,16 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv)
 		}
 
 		options.values_[c] = optarg ? optarg : "";
+
+		/* Parse argument as key=values if needed. */
+		if (optionsMap_[c].keyValueParser) {
+			options.keyValues_[c] = optionsMap_[c].keyValueParser->parse(options.values_[c].c_str());
+			if (!options.keyValues_[c].valid()) {
+				usage();
+				options.clear();
+				break;
+			}
+		}
 	}
 
 	return options;
@@ -169,5 +190,141 @@ void OptionsParser::usage()
 				std::cerr << help << std::endl;
 			}
 		}
+
+		if (option.keyValueParser)
+			option.keyValueParser->usage(indent);
+	}
+}
+
+bool OptionsParser::Options::isKeyValue(int opt)
+{
+	return keyValues_.find(opt) != keyValues_.end();
+}
+
+KeyValueParser::Options OptionsParser::Options::keyValues(int opt)
+{
+	return keyValues_.find(opt)->second;
+}
+
+void KeyValueParser::addOption(const char *name,
+			       const char *help,
+			       OptionArgument argument,
+			       const char *argumentName)
+{
+	if (!name)
+		return;
+	if (!help || help[0] == '\0')
+		return;
+	if (argument != ArgumentNone && !argumentName)
+		return;
+
+	/* Reject duplicate options. */
+	if (optionsMap_.find(name) != optionsMap_.end())
+		return;
+
+	optionsMap_[name] = Option({ name, help, argument, argumentName });
+}
+
+KeyValueParser::Options KeyValueParser::parse(const char *arguments)
+{
+	Options options;
+
+	for (const char *pair = arguments; *arguments != '\0'; pair = arguments) {
+		const char *comma = strchrnul(arguments, ',');
+		size_t len = comma - pair;
+
+		/* Skip over the comma. */
+		arguments = *comma == ',' ? comma + 1 : comma;
+
+		/* Skip to the next pair if the pair is empty. */
+		if (!len)
+			continue;
+
+		std::string key;
+		std::string value;
+
+		const char *separator = static_cast<const char *>(memchr(pair, '=', len));
+		if (!separator) {
+			key = std::string(pair, len);
+			value = "";
+		} else {
+			key = std::string(pair, separator - pair);
+			value = std::string(separator + 1, comma - separator - 1);
+		}
+
+		/* The key is mandatory, the value might be optional. */
+		if (key.empty())
+			continue;
+
+		if (optionsMap_.find(key) == optionsMap_.end()) {
+			std::cerr << "Invalid option " << key << std::endl;
+			options.clear();
+			break;
+		}
+
+		if (value == "" && optionsMap_[key].argument == ArgumentRequired) {
+			std::cerr << "Missing argument for option " << key << std::endl;
+			options.clear();
+			break;
+		}
+
+		if (value != "" && optionsMap_[key].argument == ArgumentNone) {
+			std::cerr << "Argument specified for option " << key << std::endl;
+			options.clear();
+			break;
+		}
+
+		options.values_[key] = value;
+	}
+
+	return options;
+}
+
+void KeyValueParser::usage(int indent)
+{
+	unsigned int space = 0;
+
+	for (auto const &iter : optionsMap_) {
+		const Option &option = iter.second;
+		unsigned int length = 14;
+		if (option.argument != ArgumentNone)
+			length += 1 + strlen(option.argumentName);
+		if (option.argument == ArgumentOptional)
+			length += 2;
+
+		if (length > space)
+			space = length;
+	}
+
+	space = (space + 7) / 8 * 8;
+
+	for (auto const &iter : optionsMap_) {
+		const Option &option = iter.second;
+
+		std::string argument = option.name;
+
+		if (option.argument != ArgumentNone) {
+			if (option.argument == ArgumentOptional)
+				argument += "[=";
+			else
+				argument += "=";
+			argument += option.argumentName;
+			if (option.argument == ArgumentOptional)
+				argument += "]";
+		}
+
+		std::cerr << std::setw(indent) << std::right << " "
+			  << std::setw(space) << std::left << argument;
+
+		for (const char *help = option.help, *end = help; end;) {
+			end = strchr(help, '\n');
+			if (end) {
+				std::cerr << std::string(help, end - help + 1);
+				std::cerr << std::setw(indent + space) << " ";
+				help = end + 1;
+			} else {
+				std::cerr << help << std::endl;
+			}
+		}
 	}
 }
diff --git a/src/cam/options.h b/src/cam/options.h
index cb7286a0a8005579..91a78406a601d737 100644
--- a/src/cam/options.h
+++ b/src/cam/options.h
@@ -27,20 +27,54 @@ public:
 
 private:
 	friend class OptionsParser;
+	friend class KeyValueParser;
 	std::map<T, std::string> values_;
 	void clear() { values_.clear(); };
 };
 
+class KeyValueParser
+{
+public:
+	class Options : public OptionsBase<std::string>
+	{
+	};
+
+	void addOption(const char *name, const char *help,
+		       OptionArgument argument = ArgumentNone,
+		       const char *argumentName = nullptr);
+
+	Options parse(const char *arguments);
+	void usage(int indent);
+
+private:
+	struct Option {
+		const char *name;
+		const char *help;
+		OptionArgument argument;
+		const char *argumentName;
+	};
+
+	std::map<std::string, Option> optionsMap_;
+};
+
 class OptionsParser
 {
 public:
 	class Options : public OptionsBase<int>
 	{
+	public:
+		bool isKeyValue(int opt);
+		KeyValueParser::Options keyValues(int opt);
+	private:
+		friend class OptionsParser;
+		std::map<unsigned int, KeyValueParser::Options> keyValues_;
 	};
 
 	bool addOption(int opt, const char *help, const char *name = nullptr,
 		       OptionArgument argument = ArgumentNone,
 		       const char *argumentName = nullptr);
+	void addOption(int opt, const char *help, KeyValueParser &parser,
+		       const char *name = nullptr);
 
 	Options parse(int argc, char *argv[]);
 	void usage();
@@ -52,6 +86,7 @@ private:
 		OptionArgument argument;
 		const char *argumentName;
 		const char *help;
+		KeyValueParser *keyValueParser;
 
 		bool hasShortOption() const { return isalnum(opt); }
 		bool hasLongOption() const { return name != nullptr; }
