diff --git a/bindings/cxx/Makefile.am b/bindings/cxx/Makefile.am
index 65da13f..f719072 100644
--- a/bindings/cxx/Makefile.am
+++ b/bindings/cxx/Makefile.am
@@ -15,7 +15,9 @@ libgpiodcxx_la_SOURCES =	\
 	line-config.cpp		\
 	line-info.cpp		\
 	line-request.cpp	\
+	line-settings.cpp	\
 	misc.cpp		\
+	request-builder.cpp	\
 	request-config.cpp
 
 libgpiodcxx_la_CXXFLAGS = -Wall -Wextra -g -std=gnu++17
diff --git a/bindings/cxx/chip-info.cpp b/bindings/cxx/chip-info.cpp
index e3e6f27..c4f0ab5 100644
--- a/bindings/cxx/chip-info.cpp
+++ b/bindings/cxx/chip-info.cpp
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
+#include <ostream>
+#include <utility>
+
 #include "internal.hpp"
 
 namespace gpiod {
diff --git a/bindings/cxx/chip.cpp b/bindings/cxx/chip.cpp
index 9e17a62..d6a3a43 100644
--- a/bindings/cxx/chip.cpp
+++ b/bindings/cxx/chip.cpp
@@ -1,15 +1,15 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
+#include <ostream>
+#include <utility>
+
 #include "internal.hpp"
 
 namespace gpiod {
 
 namespace {
 
-using chip_deleter = deleter<::gpiod_chip, ::gpiod_chip_close>;
-using chip_ptr = ::std::unique_ptr<::gpiod_chip, chip_deleter>;
-
 chip_ptr open_chip(const ::std::filesystem::path& path)
 {
 	chip_ptr chip(::gpiod_chip_open(path.c_str()));
@@ -21,27 +21,17 @@ chip_ptr open_chip(const ::std::filesystem::path& path)
 
 } /* namespace */
 
-struct chip::impl
+chip::impl::impl(const ::std::filesystem::path& path)
+	: chip(open_chip(path))
 {
-	impl(const ::std::filesystem::path& path)
-		: chip(open_chip(path))
-	{
-
-	}
-
-	impl(const impl& other) = delete;
-	impl(impl&& other) = delete;
-	impl& operator=(const impl& other) = delete;
-	impl& operator=(impl&& other) = delete;
 
-	void throw_if_closed() const
-	{
-		if (!this->chip)
-			throw chip_closed("GPIO chip has been closed");
-	}
+}
 
-	chip_ptr chip;
-};
+void chip::impl::throw_if_closed() const
+{
+	if (!this->chip)
+		throw chip_closed("GPIO chip has been closed");
+}
 
 GPIOD_CXX_API chip::chip(const ::std::filesystem::path& path)
 	: _m_priv(new impl(path))
@@ -49,6 +39,12 @@ GPIOD_CXX_API chip::chip(const ::std::filesystem::path& path)
 
 }
 
+chip::chip(const chip& other)
+	: _m_priv(other._m_priv)
+{
+
+}
+
 GPIOD_CXX_API chip::chip(chip&& other) noexcept
 	: _m_priv(::std::move(other._m_priv))
 {
@@ -187,21 +183,9 @@ GPIOD_CXX_API int chip::get_line_offset_from_name(const ::std::string& name) con
 	return ret;
 }
 
-GPIOD_CXX_API line_request chip::request_lines(const request_config& req_cfg,
-					       const line_config& line_cfg)
+GPIOD_CXX_API request_builder chip::prepare_request()
 {
-	this->_m_priv->throw_if_closed();
-
-	line_request_ptr request(::gpiod_chip_request_lines(this->_m_priv->chip.get(),
-							    req_cfg._m_priv->config.get(),
-							    line_cfg._m_priv->config.get()));
-	if (!request)
-		throw_from_errno("error requesting GPIO lines");
-
-	line_request ret;
-	ret._m_priv.get()->set_request_ptr(request);
-
-	return ret;
+	return request_builder(*this);
 }
 
 GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const chip& chip)
diff --git a/bindings/cxx/edge-event-buffer.cpp b/bindings/cxx/edge-event-buffer.cpp
index 0ece132..ff398f1 100644
--- a/bindings/cxx/edge-event-buffer.cpp
+++ b/bindings/cxx/edge-event-buffer.cpp
@@ -2,6 +2,8 @@
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
 #include <iterator>
+#include <ostream>
+#include <utility>
 
 #include "internal.hpp"
 
diff --git a/bindings/cxx/edge-event.cpp b/bindings/cxx/edge-event.cpp
index 275fe3b..5992934 100644
--- a/bindings/cxx/edge-event.cpp
+++ b/bindings/cxx/edge-event.cpp
@@ -2,6 +2,8 @@
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
 #include <map>
+#include <ostream>
+#include <utility>
 
 #include "internal.hpp"
 
diff --git a/bindings/cxx/examples/gpiogetcxx.cpp b/bindings/cxx/examples/gpiogetcxx.cpp
index 0136f5f..b0d4a7d 100644
--- a/bindings/cxx/examples/gpiogetcxx.cpp
+++ b/bindings/cxx/examples/gpiogetcxx.cpp
@@ -20,13 +20,15 @@ int main(int argc, char **argv)
 	for (int i = 2; i < argc; i++)
 		offsets.push_back(::std::stoul(argv[i]));
 
-	::gpiod::chip chip(argv[1]);
-	auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ ::gpiod::request_config::property::OFFSETS, offsets },
-				{ ::gpiod::request_config::property::CONSUMER, "gpiogetcxx" }
-			}),
-			::gpiod::line_config());
+	auto request = ::gpiod::chip(argv[1])
+		.prepare_request()
+		.set_consumer("gpiogetcxx")
+		.add_line_settings(
+			offsets,
+			::gpiod::line_settings()
+				.set_direction(::gpiod::line::direction::INPUT)
+		)
+		.do_request();
 
 	auto vals = request.get_values();
 
diff --git a/bindings/cxx/examples/gpiomoncxx.cpp b/bindings/cxx/examples/gpiomoncxx.cpp
index a79138e..c351567 100644
--- a/bindings/cxx/examples/gpiomoncxx.cpp
+++ b/bindings/cxx/examples/gpiomoncxx.cpp
@@ -42,27 +42,16 @@ int main(int argc, char **argv)
 	for (int i = 2; i < argc; i++)
 		offsets.push_back(::std::stoul(argv[i]));
 
-	::gpiod::chip chip(argv[1]);
-	auto request = chip.request_lines(
-		::gpiod::request_config(
-			{
-				{ ::gpiod::request_config::property::OFFSETS, offsets},
-				{ ::gpiod::request_config::property::CONSUMER, "gpiomoncxx"},
-			}
-		),
-		::gpiod::line_config(
-			{
-				{
-					::gpiod::line_config::property::DIRECTION,
-					::gpiod::line::direction::INPUT
-				},
-				{
-					::gpiod::line_config::property::EDGE_DETECTION,
-					::gpiod::line::edge::BOTH
-				}
-			}
+	auto request = ::gpiod::chip(argv[1])
+		.prepare_request()
+		.set_consumer("gpiomoncxx")
+		.add_line_settings(
+			offsets,
+			::gpiod::line_settings()
+				.set_direction(::gpiod::line::direction::INPUT)
+				.set_edge_detection(::gpiod::line::edge::BOTH)
 		)
-	);
+		.do_request();
 
 	::gpiod::edge_event_buffer buffer;
 
diff --git a/bindings/cxx/examples/gpiosetcxx.cpp b/bindings/cxx/examples/gpiosetcxx.cpp
index 838d801..dde5379 100644
--- a/bindings/cxx/examples/gpiosetcxx.cpp
+++ b/bindings/cxx/examples/gpiosetcxx.cpp
@@ -36,18 +36,15 @@ int main(int argc, char **argv)
 						       ::gpiod::line::value::INACTIVE);
 	}
 
-	::gpiod::chip chip(argv[1]);
-	auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ ::gpiod::request_config::property::OFFSETS, offsets },
-				{ ::gpiod::request_config::property::CONSUMER, "gpiogetcxx" }
-			}),
-			::gpiod::line_config({
-				{
-					::gpiod::line_config::property::DIRECTION,
-					::gpiod::line::direction::OUTPUT
-				}
-			}));
+	auto request = ::gpiod::chip(argv[1])
+		.prepare_request()
+		.set_consumer("gpiosetcxx")
+		.add_line_settings(
+			offsets,
+			::gpiod::line_settings()
+				.set_direction(::gpiod::line::direction::OUTPUT)
+		)
+		.do_request();
 
 	request.set_values(values);
 
diff --git a/bindings/cxx/gpiod.hpp b/bindings/cxx/gpiod.hpp
index c5645b2..8981db4 100644
--- a/bindings/cxx/gpiod.hpp
+++ b/bindings/cxx/gpiod.hpp
@@ -40,6 +40,8 @@
 #include "gpiodcxx/line-config.hpp"
 #include "gpiodcxx/line-info.hpp"
 #include "gpiodcxx/line-request.hpp"
+#include "gpiodcxx/line-settings.hpp"
+#include "gpiodcxx/request-builder.hpp"
 #include "gpiodcxx/request-config.hpp"
 #undef __LIBGPIOD_GPIOD_CXX_INSIDE__
 
diff --git a/bindings/cxx/gpiodcxx/Makefile.am b/bindings/cxx/gpiodcxx/Makefile.am
index 71532e6..e3a3b9b 100644
--- a/bindings/cxx/gpiodcxx/Makefile.am
+++ b/bindings/cxx/gpiodcxx/Makefile.am
@@ -13,6 +13,8 @@ otherinclude_HEADERS = \
 	line-config.hpp \
 	line-info.hpp \
 	line-request.hpp \
+	line-settings.hpp \
 	misc.hpp \
+	request-builder.hpp \
 	request-config.hpp \
 	timestamp.hpp
diff --git a/bindings/cxx/gpiodcxx/chip.hpp b/bindings/cxx/gpiodcxx/chip.hpp
index 5bfcc99..8c2f07a 100644
--- a/bindings/cxx/gpiodcxx/chip.hpp
+++ b/bindings/cxx/gpiodcxx/chip.hpp
@@ -27,6 +27,7 @@ class info_event;
 class line_config;
 class line_info;
 class line_request;
+class request_builder;
 class request_config;
 
 /**
@@ -48,8 +49,6 @@ public:
 	 */
 	explicit chip(const ::std::filesystem::path& path);
 
-	chip(const chip& other) = delete;
-
 	/**
 	 * @brief Move constructor.
 	 * @param other Object to move.
@@ -147,19 +146,20 @@ public:
 	int get_line_offset_from_name(const ::std::string& name) const;
 
 	/**
-	 * @brief Request a set of lines for exclusive usage.
-	 * @param req_cfg Request config object.
-	 * @param line_cfg Line config object.
-	 * @return New line_request object.
+	 * @brief Create a request_builder associated with this chip.
+	 * @return New request_builder object.
 	 */
-	line_request request_lines(const request_config& req_cfg,
-				   const line_config& line_cfg);
+	request_builder prepare_request();
 
 private:
 
 	struct impl;
 
-	::std::unique_ptr<impl> _m_priv;
+	::std::shared_ptr<impl> _m_priv;
+
+	chip(const chip& other);
+
+	friend request_builder;
 };
 
 /**
diff --git a/bindings/cxx/gpiodcxx/line-config.hpp b/bindings/cxx/gpiodcxx/line-config.hpp
index ac47a99..a917913 100644
--- a/bindings/cxx/gpiodcxx/line-config.hpp
+++ b/bindings/cxx/gpiodcxx/line-config.hpp
@@ -12,18 +12,14 @@
 #error "Only gpiod.hpp can be included directly."
 #endif
 
-#include <any>
-#include <chrono>
-#include <cstddef>
-#include <iostream>
 #include <map>
 #include <memory>
-#include <utility>
 
 namespace gpiod {
 
 class chip;
 class line_request;
+class line_settings;
 
 /**
  * @ingroup gpiod_cxx
@@ -38,63 +34,8 @@ class line_config
 {
 public:
 
-	/**
-	 * @brief List of available configuration properties. Used in the
-	 *        constructor, :line_config::set_property_default and
-	 *        :line_config::set_property_override.
-	 */
-	enum class property {
-		DIRECTION = 1,
-		/**< Line direction. */
-		EDGE_DETECTION,
-		/**< Edge detection. */
-		BIAS,
-		/**< Bias. */
-		DRIVE,
-		/**< Drive. */
-		ACTIVE_LOW,
-		/**< Active-low setting. */
-		DEBOUNCE_PERIOD,
-		/**< Debounce period. */
-		EVENT_CLOCK,
-		/**< Event clock. */
-		OUTPUT_VALUE,
-		/**< Output value. */
-		OUTPUT_VALUES,
-		/**< Set of offset-to-value mappings. Only used in the constructor. */
-	};
-
-	/**
-	 * @brief List of configuration properties passed to the constructor.
-	 *        The first member is the property indicator, the second is
-	 *        the value stored as `std::any` that is interpreted by the
-	 *        relevant methods depending on the property value.
-	 */
-	using properties = ::std::map<property, ::std::any>;
-
-	/**
-	 * @brief Stored information about a single configuration override. The
-	 *        first member is the overridden line offset, the second is
-	 *        the property being overridden.
-	 */
-	using prop_override = ::std::pair<line::offset, property>;
-
-	/**
-	 * @brief List of line configuration overrides.
-	 */
-	using override_list = ::std::vector<prop_override>;
 
-	/**
-	 * @brief Constructor.
-	 * @param props List of configuration properties. See
-	 *              :set_property_default for details. Additionally the
-	 *              constructor takes another property type as argument:
-	 *              :property::OUTPUT_VALUES which takes
-	 *              :line::value_mappings as property value. This
-	 *              effectively sets the overrides for output values for
-	 *              the mapped offsets.
-	 */
-	explicit line_config(const properties& props = properties());
+	line_config();
 
 	line_config(const line_config& other) = delete;
 
@@ -106,8 +47,6 @@ public:
 
 	~line_config();
 
-	line_config& operator=(const line_config& other) = delete;
-
 	/**
 	 * @brief Move assignment operator.
 	 * @param other Object to move.
@@ -117,434 +56,44 @@ public:
 
 	/**
 	 * @brief Reset the line config object.
+	 * @return Reference to self.
 	 */
-	void reset() noexcept;
-
-	/**
-	 * @brief Set the default value of a single configuration property.
-	 * @param prop Property to set.
-	 * @param val Property value. The type must correspond with the
-	 *            property being set: :line::direction for
-	 *            :property::DIRECTION, :line::edge for :property::EDGE,
-	 *            :line::bias for :property::BIAS, :line::drive for
-	 *            :property::DRIVE, `bool` for :property::ACTIVE_LOW,
-	 *            `std::chrono:microseconds` for
-	 *            :property::DEBOUNCE_PERIOD, :line::clock for
-	 *            :property::EVENT_CLOCK and :line::value
-	 *            for :property::OUTPUT_VALUE.
-	 *
-	 */
-	void set_property_default(property prop, const ::std::any& val);
-
-	/**
-	 * @brief Set the override value of a single configuration property.
-	 * @param prop Property to set.
-	 * @param offset Line offset to override.
-	 * @param val Property value. See :set_property_default for details.
-	 */
-	void set_property_offset(property prop, line::offset offset, const ::std::any& val);
-
-	/**
-	 * @brief Set the default direction setting.
-	 * @param direction New direction.
-	 */
-	void set_direction_default(line::direction direction);
-
-	/**
-	 * @brief Set the direction for a single line at given offset.
-	 * @param direction New direction.
-	 * @param offset Offset of the line for which to set the direction.
-	 */
-	void set_direction_override(line::direction direction, line::offset offset);
-
-	/**
-	 * @brief Get the default direction setting.
-	 * @return Direction setting that would have been used for any offset
-	 * 	   not assigned its own direction value.
-	 */
-	line::direction direction_default() const;
-
-	/**
-	 * @brief Get the direction setting for a given offset.
-	 * @param offset Line offset for which to read the direction setting.
-	 * @return Direction setting that would have been used for given offset
-	 *         if the config object was used in a request at the time of
-	 *         the call.
-	 */
-	line::direction direction_offset(line::offset offset) const;
-
-	/**
-	 * @brief Clear the direction override at given offset.
-	 * @param offset Offset of the line for which to clear the override.
-	 * @note Does nothing if no override is set for this line.
-	 */
-	void clear_direction_override(line::offset offset) noexcept;
-
-	/**
-	 * @brief Check if the direction setting is overridden at given offset.
-	 * @param offset Offset of the line for which to check the override.
-	 * @return True if direction is overridden at this offset, false
-	 *         otherwise.
-	 */
-	bool direction_is_overridden(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Set the default edge event detection.
-	 * @param edge Type of edge events to detect.
-	 */
-	void set_edge_detection_default(line::edge edge);
-
-	/**
-	 * @brief Set the edge event detection for a single line at given
-	 *        offset.
-	 * @param edge Type of edge events to detect.
-	 * @param offset Offset of the line for which to set the direction.
-	 */
-	void set_edge_detection_override(line::edge edge, line::offset offset);
-
-	/**
-	 * @brief Get the default edge detection setting.
-	 * @return Edge detection setting that would have been used for any
-	 *         offset not assigned its own direction value.
-	 */
-	line::edge edge_detection_default() const;
-
-	/**
-	 * @brief Get the edge event detection setting for a given offset.
-	 * @param offset Line offset for which to read the edge detection
-	 *               setting.
-	 * @return Edge event detection setting that would have been used for
-	 * 	   given offset if the config object was used in a request at
-	 * 	   the time of the call.
-	 */
-	line::edge edge_detection_offset(line::offset offset) const;
-
-	/**
-	 * @brief Clear the edge detection override at given offset.
-	 * @param offset Offset of the line for which to clear the override.
-	 * @note Does nothing if no override is set for this line.
-	 */
-	void clear_edge_detection_override(line::offset offset) noexcept;
-
-	/**
-	 * @brief Check if the edge detection setting is overridden at given
-	 *        offset.
-	 * @param offset Offset of the line for which to check the override.
-	 * @return True if edge detection is overridden at this offset, false
-	 *         otherwise.
-	 */
-	bool edge_detection_is_overridden(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Set the default bias setting.
-	 * @param bias New bias.
-	 */
-	void set_bias_default(line::bias bias);
-
-	/**
-	 * @brief Set the bias for a single line at given offset.
-	 * @param bias New bias.
-	 * @param offset Offset of the line for which to set the bias.
-	 */
-	void set_bias_override(line::bias bias, line::offset offset);
-
-	/**
-	 * @brief Get the default bias setting.
-	 * @return Bias setting that would have been used for any offset not
-	 *         assigned its own direction value.
-	 */
-	line::bias bias_default() const;
-
-	/**
-	 * @brief Get the bias setting for a given offset.
-	 * @param offset Line offset for which to read the bias setting.
-	 * @return Bias setting that would have been used for given offset if
-	 *         the config object was used in a request at the time of the
-	 *         call.
-	 */
-	line::bias bias_offset(line::offset offset) const;
-
-	/**
-	 * @brief Clear the bias override at given offset.
-	 * @param offset Offset of the line for which to clear the override.
-	 * @note Does nothing if no override is set for this line.
-	 */
-	void clear_bias_override(line::offset offset) noexcept;
-
-	/**
-	 * @brief Check if the bias setting is overridden at given offset.
-	 * @param offset Offset of the line for which to check the override.
-	 * @return True if bias is overridden at this offset, false otherwise.
-	 */
-	bool bias_is_overridden(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Set the default drive setting.
-	 * @param drive New drive.
-	 */
-	void set_drive_default(line::drive drive);
-
-	/**
-	 * @brief Set the drive for a single line at given offset.
-	 * @param drive New drive.
-	 * @param offset Offset of the line for which to set the drive.
-	 */
-	void set_drive_override(line::drive drive, line::offset offset);
-
-	/**
-	 * @brief Set the drive for a subset of offsets.
-	 * @param drive New drive.
-	 * @param offsets Vector of line offsets for which to set the drive.
-	 */
-	void set_drive(line::drive drive, const line::offsets& offsets);
-
-	/**
-	 * @brief Get the default drive setting.
-	 * @return Drive setting that would have been used for any offset not
-	 *         assigned its own direction value.
-	 */
-	line::drive drive_default() const;
-
-	/**
-	 * @brief Get the drive setting for a given offset.
-	 * @param offset Line offset for which to read the drive setting.
-	 * @return Drive setting that would have been used for given offset if
-	 *         the config object was used in a request at the time of the
-	 *         call.
-	 */
-	line::drive drive_offset(line::offset offset) const;
-
-	/**
-	 * @brief Clear the drive override at given offset.
-	 * @param offset Offset of the line for which to clear the override.
-	 * @note Does nothing if no override is set for this line.
-	 */
-	void clear_drive_override(line::offset offset) noexcept;
-
-	/**
-	 * @brief Check if the drive setting is overridden at given offset.
-	 * @param offset Offset of the line for which to check the override.
-	 * @return True if drive is overridden at this offset, false otherwise.
-	 */
-	bool drive_is_overridden(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Set lines to active-low by default.
-	 * @param active_low New active-low setting.
-	 */
-	void set_active_low_default(bool active_low) noexcept;
-
-	/**
-	 * @brief Set a single line as active-low.
-	 * @param active_low New active-low setting.
-	 * @param offset Offset of the line for which to set the active setting.
-	 */
-	void set_active_low_override(bool active_low, line::offset offset) noexcept;
-
-	/**
-	 * @brief Check if active-low is the default setting.
-	 * @return Active-low setting that would have been used for any offset
-         *         not assigned its own value.
-	 */
-	bool active_low_default() const noexcept;
-
-	/**
-	 * @brief Check if the line at given offset was configured as
-	 *        active-low.
-	 * @param offset Line offset for which to read the active-low setting.
-	 * @return Active-low setting that would have been used for given
-	 *         offset if the config object was used in a request at the
-	 *         time of the call.
-	 */
-	bool active_low_offset(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Clear the active-low override at given offset.
-	 * @param offset Offset of the line for which to clear the override.
-	 * @note Does nothing if no override is set for this line.
-	 */
-	void clear_active_low_override(line::offset offset) noexcept;
-
-	/**
-	 * @brief Check if the active-low setting is overridden at given offset.
-	 * @param offset Offset of the line for which to check the override.
-	 * @return True if active-low is overridden at this offset, false
-	 *         otherwise.
-	 */
-	bool active_low_is_overridden(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Set the default debounce period.
-	 * @param period New debounce period. Disables debouncing if 0.
-	 */
-	void set_debounce_period_default(const ::std::chrono::microseconds& period) noexcept;
-
-	/**
-	 * @brief Set the debounce period for a single line at given offset.
-	 * @param period New debounce period. Disables debouncing if 0.
-	 * @param offset Offset of the line for which to set the debounce
-	 *               period.
-	 */
-	void set_debounce_period_override(const ::std::chrono::microseconds& period,
-					     line::offset offset) noexcept;
-
-	/**
-	 * @brief Get the default debounce period.
-	 * @return Debounce period that would have been used for any offset not
-	 *         assigned its own debounce period. 0 if not debouncing is
-	 *         disabled.
-	 */
-	::std::chrono::microseconds debounce_period_default() const noexcept;
-
-	/**
-	 * @brief Get the debounce period for a given offset.
-	 * @param offset Line offset for which to read the debounce period.
-	 * @return Debounce period that would have been used for given offset
-	 *         if the config object was used in a request at the time of
-	 *         the call. 0 if debouncing is disabled.
-	 */
-	::std::chrono::microseconds debounce_period_offset(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Clear the debounce period override at given offset.
-	 * @param offset Offset of the line for which to clear the override.
-	 * @note Does nothing if no override is set for this line.
-	 */
-	void clear_debounce_period_override(line::offset offset) noexcept;
-
-	/**
-	 * @brief Check if the debounce period setting is overridden at given offset.
-	 * @param offset Offset of the line for which to check the override.
-	 * @return True if debounce period is overridden at this offset, false
-	 *         otherwise.
-	 */
-	bool debounce_period_is_overridden(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Set the default event timestamp clock.
-	 * @param clock New clock to use.
-	 */
-	void set_event_clock_default(line::clock clock);
-
-	/**
-	 * @brief Set the event clock for a single line at given offset.
-	 * @param clock New clock to use.
-	 * @param offset Offset of the line for which to set the event clock
-	 *               type.
-	 */
-	void set_event_clock_override(line::clock clock, line::offset offset);
-
-	/**
-	 * @brief Get the default event clock setting.
-	 * @return Event clock setting that would have been used for any offset
-	 *         not assigned its own direction value.
-	 */
-	line::clock event_clock_default() const;
-
-	/**
-	 * @brief Get the event clock setting for a given offset.
-	 * @param offset Line offset for which to read the event clock setting.
-	 * @return Event clock setting that would have been used for given
-	 *         offset if the config object was used in a request at the
-	 *         time of the call.
-	 */
-	line::clock event_clock_offset(line::offset offset) const;
-
-	/**
-	 * @brief Clear the event clock override at given offset.
-	 * @param offset Offset of the line for which to clear the override.
-	 * @note Does nothing if no override is set for this line.
-	 */
-	void clear_event_clock_override(line::offset offset) noexcept;
-
-	/**
-	 * @brief Check if the event clock setting is overridden at given
-	 *        offset.
-	 * @param offset Offset of the line for which to check the override.
-	 * @return True if event clock is overridden at this offset, false
-	 *         otherwise.
-	 */
-	bool event_clock_is_overridden(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Set the default output value.
-	 * @param value New value.
-	 */
-	void set_output_value_default(line::value value) noexcept;
-
-	/**
-	 * @brief Set the output value for a single offset.
-	 * @param offset Line offset to associate the value with.
-	 * @param value New value.
-	 */
-	void set_output_value_override(line::value value, line::offset offset) noexcept;
-
-	/**
-	 * @brief Set the output values for a set of line offsets.
-	 * @param values Vector of offset->value mappings.
-	 */
-	void set_output_values(const line::value_mappings& values);
-
-	/**
-	 * @brief Set the output values for a set of line offsets.
-	 * @param offsets Vector of line offsets for which to set output values.
-	 * @param values Vector of new line values with indexes of values
-	 *               corresponding to the indexes of offsets.
-	 */
-	void set_output_values(const line::offsets& offsets, const line::values& values);
-
-	/**
-	 * @brief Get the default output value.
-	 * @return Output value that would have been used for any offset not
-	 *         assigned its own output value.
-	 */
-	line::value output_value_default() const noexcept;
-
-	/**
-	 * @brief Get the output value configured for a given line.
-	 * @param offset Line offset for which to read the value.
-	 * @return Output value that would have been used for given offset if
-	 *         the config object was used in a request at the time of the
-	 *         call.
-	 */
-	line::value output_value_offset(line::offset offset) const noexcept;
-
-	/**
-	 * @brief Clear the output value override at given offset.
-	 * @param offset Offset of the line for which to clear the override.
-	 * @note Does nothing if no override is set for this line.
-	 */
-	void clear_output_value_override(line::offset offset) noexcept;
+	line_config& reset() noexcept;
 
 	/**
-	 * @brief Check if the output value setting is overridden at given
-	 *        offset.
-	 * @param offset Offset of the line for which to check the override.
-	 * @return True if output value is overridden at this offset, false
-	 *         otherwise.
+	 * @brief Add line settings for a single offset.
+	 * @param offset Offset for which to add settings.
+	 * @param settings Line settings to add.
+	 * @return Reference to self.
 	 */
-	bool output_value_is_overridden(line::offset offset) const noexcept;
+	line_config& add_line_settings(line::offset offset, const line_settings& settings);
 
 	/**
-	 * @brief Get the number of configuration overrides.
-	 * @return Number of overrides held by this object.
+	 * @brief Add line settings for a set of offsets.
+	 * @param offsets Offsets for which to add settings.
+	 * @param settings Line settings to add.
+	 * @return Reference to self.
 	 */
-	::std::size_t num_overrides() const noexcept;
+	line_config& add_line_settings(const line::offsets& offsets, const line_settings& settings);
 
 	/**
-	 * @brief Get the list of property overrides.
-	 * @return List of configuration property overrides held by this object.
+	 * @brief Get a mapping of offsets to line settings stored by this
+	 *        object.
+	 * @return Map in which keys represent line offsets and values are
+	 *         the settings corresponding with them.
 	 */
-	override_list overrides() const;
+	::std::map<line::offset, line_settings> get_line_settings() const;
 
 private:
 
 	struct impl;
 
-	::std::unique_ptr<impl> _m_priv;
+	::std::shared_ptr<impl> _m_priv;
+
+	line_config& operator=(const line_config& other);
 
-	friend chip;
 	friend line_request;
+	friend request_builder;
 };
 
 /**
diff --git a/bindings/cxx/gpiodcxx/line-request.hpp b/bindings/cxx/gpiodcxx/line-request.hpp
index be0c71f..659251b 100644
--- a/bindings/cxx/gpiodcxx/line-request.hpp
+++ b/bindings/cxx/gpiodcxx/line-request.hpp
@@ -53,6 +53,7 @@ public:
 	/**
 	 * @brief Move assignment operator.
 	 * @param other Object to move.
+	 * @return Reference to self.
 	 */
 	line_request& operator=(line_request&& other) noexcept;
 
@@ -130,35 +131,40 @@ public:
 	 * @brief Set the value of a single requested line.
 	 * @param offset Offset of the line to set within the chip.
 	 * @param value New line value.
+	 * @return Reference to self.
 	 */
-	void set_value(line::offset offset, line::value value);
+	line_request& set_value(line::offset offset, line::value value);
 
 	/**
 	 * @brief Set the values of a subset of requested lines.
 	 * @param values Vector containing a set of offset->value mappings.
+	 * @return Reference to self.
 	 */
-	void set_values(const line::value_mappings& values);
+	line_request& set_values(const line::value_mappings& values);
 
 	/**
 	 * @brief Set the values of a subset of requested lines.
 	 * @param offsets Vector containing the offsets of lines to set.
 	 * @param values Vector containing new values with indexes
 	 *               corresponding with those in the offsets vector.
+	 * @return Reference to self.
 	 */
-	void set_values(const line::offsets& offsets, const line::values& values);
+	line_request& set_values(const line::offsets& offsets, const line::values& values);
 
 	/**
 	 * @brief Set the values of all requested lines.
 	 * @param values Array of new line values. The size must be equal to
 	 *               the value returned by line_request::num_lines.
+	 * @return Reference to self.
 	 */
-	void set_values(const line::values& values);
+	line_request& set_values(const line::values& values);
 
 	/**
 	 * @brief Apply new config options to requested lines.
 	 * @param config New configuration.
+	 * @return Reference to self.
 	 */
-	void reconfigure_lines(const line_config& config);
+	line_request& reconfigure_lines(const line_config& config);
 
 	/**
 	 * @brief Get the file descriptor number associated with this line
@@ -201,7 +207,7 @@ private:
 
 	::std::unique_ptr<impl> _m_priv;
 
-	friend chip;
+	friend request_builder;
 };
 
 /**
diff --git a/bindings/cxx/gpiodcxx/line-settings.hpp b/bindings/cxx/gpiodcxx/line-settings.hpp
new file mode 100644
index 0000000..c1477b1
--- /dev/null
+++ b/bindings/cxx/gpiodcxx/line-settings.hpp
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file request-config.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_SETTINGS_HPP__
+#define __LIBGPIOD_CXX_LINE_SETTINGS_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <memory>
+
+#include "line.hpp"
+
+namespace gpiod {
+
+class line_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores GPIO line settings.
+ */
+class line_settings
+{
+public:
+
+	/**
+	 * @brief Initializes the line_settings object with default values.
+	 */
+	line_settings();
+
+	line_settings(const line_settings& other) = delete;
+
+	/**
+	 * @brief Move constructor.
+	 * @param other Object to move.
+	 */
+	line_settings(line_settings&& other) noexcept;
+
+	~line_settings();
+
+	line_settings& operator=(const line_settings& other) = delete;
+
+	/**
+	 * @brief Move assignment operator.
+	 * @param other Object to move.
+	 * @return Reference to self.
+	 */
+	line_settings& operator=(line_settings&& other);
+
+	/**
+	 * @brief Reset the line settings to default values.
+	 * @return Reference to self.
+	 */
+	line_settings& reset(void) noexcept;
+
+	/**
+	 * @brief Set direction.
+	 * @param direction New direction.
+	 * @return Reference to self.
+	 */
+	line_settings& set_direction(line::direction direction);
+
+	/**
+	 * @brief Get direction.
+	 * @return Current direction setting.
+	 */
+	line::direction direction() const;
+
+	/**
+	 * @brief Set edge detection.
+	 * @param edge New edge detection setting.
+	 * @return Reference to self.
+	 */
+	line_settings& set_edge_detection(line::edge edge);
+
+	/**
+	 * @brief Get edge detection.
+	 * @return Current edge detection setting.
+	 */
+	line::edge edge_detection() const;
+
+	/**
+	 * @brief Set bias setting.
+	 * @param bias New bias.
+	 * @return Reference to self.
+	 */
+	line_settings& set_bias(line::bias bias);
+
+	/**
+	 * @brief Get bias setting.
+	 * @return Current bias.
+	 */
+	line::bias bias() const;
+
+	/**
+	 * @brief Set drive setting.
+	 * @param drive New drive.
+	 * @return Reference to self.
+	 */
+	line_settings& set_drive(line::drive drive);
+
+	/**
+	 * @brief Get drive setting.
+	 * @return Current drive.
+	 */
+	line::drive drive() const;
+
+	/**
+	 * @brief Set the active-low setting.
+	 * @param active_low New active-low setting.
+	 * @return Reference to self.
+	 */
+	line_settings& set_active_low(bool active_low);
+
+	/**
+	 * @brief Get the active-low setting.
+	 * @return Current active-low setting.
+	 */
+	bool active_low() const noexcept;
+
+	/**
+	 * @brief Set debounce period.
+	 * @param period New debounce period in microseconds.
+	 * @return Reference to self.
+	 */
+	line_settings& set_debounce_period(const ::std::chrono::microseconds& period);
+
+	/**
+	 * @brief Get debounce period.
+	 * @return Current debounce period.
+	 */
+	::std::chrono::microseconds debounce_period() const noexcept;
+
+	/**
+	 * @brief Set the event clock to use for edge event timestamps.
+	 * @param event_clock Clock to use.
+	 * @return Reference to self.
+	 */
+	line_settings& set_event_clock(line::clock event_clock);
+
+	/**
+	 * @brief Get the event clock used for edge event timestamps.
+	 * @return Current event clock type.
+	 */
+	line::clock event_clock() const;
+
+	/**
+	 * @brief Set the output value.
+	 * @param value New output value.
+	 * @return Reference to self.
+	 */
+	line_settings& set_output_value(line::value value);
+
+	/**
+	 * @brief Get the output value.
+	 * @return Current output value.
+	 */
+	line::value output_value() const;
+
+private:
+
+	struct impl;
+
+	::std::unique_ptr<impl> _m_priv;
+
+	friend line_config;
+};
+
+/**
+ * @brief Stream insertion operator for line settings.
+ * @param out Output stream to write to.
+ * @param settings Line settings object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_settings& settings);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_SETTINGS_HPP__ */
diff --git a/bindings/cxx/gpiodcxx/request-builder.hpp b/bindings/cxx/gpiodcxx/request-builder.hpp
new file mode 100644
index 0000000..d3ada53
--- /dev/null
+++ b/bindings/cxx/gpiodcxx/request-builder.hpp
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file request-builder.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__
+#define __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <memory>
+#include <ostream>
+
+namespace gpiod {
+
+class chip;
+class line_config;
+class line_request;
+class request_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Intermediate object storing the configuration for a line request.
+ */
+class request_builder
+{
+public:
+
+	request_builder(const request_builder& other) = delete;
+
+	/**
+	 * @brief Move constructor.
+	 * @param other Object to be moved.
+	 */
+	request_builder(request_builder&& other) noexcept;
+
+	~request_builder();
+
+	request_builder& operator=(const request_builder& other) = delete;
+
+	/**
+	 * @brief Move assignment operator.
+	 * @param other Object to be moved.
+	 * @return Reference to self.
+	 */
+	request_builder& operator=(request_builder&& other) noexcept;
+
+	/**
+	 * @brief Set the request config for the request.
+	 * @param req_cfg Request config to use.
+	 * @return Reference to self.
+	 */
+	request_builder& set_request_config(request_config& req_cfg);
+
+	/**
+	 * @brief Get the current request config.
+	 * @return Const reference to the current request config stored by this
+	 *         object.
+	 */
+	const request_config& get_request_config() const noexcept;
+
+	/**
+	 * @brief Set consumer in the request config stored by this object.
+	 * @param consumer New consumer string.
+	 * @return Reference to self.
+	 */
+	request_builder& set_consumer(const ::std::string& consumer) noexcept;
+
+	/**
+	 * @brief Set the event buffer size in the request config stored by
+	 *        this object.
+	 * @param event_buffer_size New event buffer size.
+	 * @return Reference to self.
+	 */
+	request_builder& set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
+
+	/**
+	 * @brief Set the line config for this request.
+	 * @param line_cfg Line config to use.
+	 * @return Reference to self.
+	 */
+	request_builder& set_line_config(line_config &line_cfg);
+
+	/**
+	 * @brief Get the current line config.
+	 * @return Const reference to the current line config stored by this
+	 *         object.
+	 */
+	const line_config& get_line_config() const noexcept;
+
+	/**
+	 * @brief Add line settings to the line config stored by this object
+	 *        for a single offset.
+	 * @param offset Offset for which to add settings.
+	 * @param settings Line settings to use.
+	 * @return Reference to self.
+	 */
+	request_builder& add_line_settings(line::offset offset, const line_settings& settings);
+
+	/**
+	 * @brief Add line settings to the line config stored by this object
+	 *        for a set of offsets.
+	 * @param offsets Offsets for which to add settings.
+	 * @param settings Settings to add.
+	 * @return Reference to self.
+	 */
+	request_builder& add_line_settings(const line::offsets& offsets, const line_settings& settings);
+
+	/**
+	 * @brief Make the line request.
+	 * @return New line_request object.
+	 */
+	line_request do_request();
+
+private:
+
+	struct impl;
+
+	request_builder(chip& chip);
+
+	::std::unique_ptr<impl> _m_priv;
+
+	friend chip;
+	friend ::std::ostream& operator<<(::std::ostream& out, const request_builder& builder);
+};
+
+/**
+ * @brief Stream insertion operator for GPIO request builder objects.
+ * @param out Output stream to write to.
+ * @param builder Request builder object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const request_builder& builder);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__ */
diff --git a/bindings/cxx/gpiodcxx/request-config.hpp b/bindings/cxx/gpiodcxx/request-config.hpp
index b77d7db..70d179e 100644
--- a/bindings/cxx/gpiodcxx/request-config.hpp
+++ b/bindings/cxx/gpiodcxx/request-config.hpp
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: LGPL-3.0-or-later */
-/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
 
 /**
  * @file request-config.hpp
@@ -12,10 +12,8 @@
 #error "Only gpiod.hpp can be included directly."
 #endif
 
-#include <any>
 #include <cstddef>
 #include <iostream>
-#include <map>
 #include <memory>
 #include <string>
 
@@ -38,30 +36,10 @@ class request_config
 {
 public:
 
-	/**
-	 * @brief List of available configuration settings. Used in the
-	 *        constructor and :request_config::set_property.
-	 */
-	enum class property {
-		OFFSETS = 1,
-		/**< List of line offsets to request. */
-		CONSUMER,
-		/**< Consumer string. */
-		EVENT_BUFFER_SIZE,
-		/**< Suggested size of the edge event buffer. */
-	};
-
-	/**
-	 * @brief Map of mappings between property types and property values.
-	 */
-	using properties = ::std::map<property, ::std::any>;
-
 	/**
 	 * @brief Constructor.
-	 * @param props List of config properties. See
-	 *              :request_config::set_property.
 	 */
-	explicit request_config(const properties& props = properties());
+	request_config();
 
 	request_config(const request_config& other) = delete;
 
@@ -73,8 +51,6 @@ public:
 
 	~request_config();
 
-	request_config& operator=(const request_config& other) = delete;
-
 	/**
 	 * @brief Move assignment operator.
 	 * @param other Object to move.
@@ -82,34 +58,12 @@ public:
 	 */
 	request_config& operator=(request_config&& other) noexcept;
 
-	/**
-	 * @brief Set the value of a single config property.
-	 * @param prop Property to set.
-	 * @param val Property value. The type must correspond to the property
-	 *            being set: `std::string` or `const char*` for
-	 *            :property::CONSUMER, `:line::offsets` for
-	 *            :property::OFFSETS and `unsigned long` for
-	 *            :property::EVENT_BUFFER_SIZE.
-	 */
-	void set_property(property prop, const ::std::any& val);
-
-	/**
-	 * @brief Set line offsets for this request.
-	 * @param offsets Vector of line offsets to request.
-	 */
-	void set_offsets(const line::offsets& offsets) noexcept;
-
-	/**
-	 * @brief Get the number of offsets configured in this request config.
-	 * @return Number of line offsets in this request config.
-	 */
-	::std::size_t num_offsets() const noexcept;
-
 	/**
 	 * @brief Set the consumer name.
 	 * @param consumer New consumer name.
+	 * @return Reference to self.
 	 */
-	void set_consumer(const ::std::string& consumer) noexcept;
+	request_config& set_consumer(const ::std::string& consumer) noexcept;
 
 	/**
 	 * @brief Get the consumer name.
@@ -117,19 +71,14 @@ public:
 	 */
 	::std::string consumer() const noexcept;
 
-	/**
-	 * @brief Get the hardware offsets of lines in this request config.
-	 * @return List of line offsets.
-	 */
-	line::offsets offsets() const;
-
 	/**
 	 * @brief Set the size of the kernel event buffer.
 	 * @param event_buffer_size New event buffer size.
+	 * @return Reference to self.
 	 * @note The kernel may adjust the value if it's too high. If set to 0,
 	 *       the default value will be used.
 	 */
-	void set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
+	request_config& set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
 
 	/**
 	 * @brief Get the edge event buffer size from this request config.
@@ -141,9 +90,11 @@ private:
 
 	struct impl;
 
-	::std::unique_ptr<impl> _m_priv;
+	::std::shared_ptr<impl> _m_priv;
+
+	request_config& operator=(const request_config& other);
 
-	friend chip;
+	friend request_builder;
 };
 
 /**
diff --git a/bindings/cxx/info-event.cpp b/bindings/cxx/info-event.cpp
index 8e99f9c..d9e14a3 100644
--- a/bindings/cxx/info-event.cpp
+++ b/bindings/cxx/info-event.cpp
@@ -2,6 +2,7 @@
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
 #include <map>
+#include <ostream>
 
 #include "internal.hpp"
 
diff --git a/bindings/cxx/internal.hpp b/bindings/cxx/internal.hpp
index 8b3c2f8..0703474 100644
--- a/bindings/cxx/internal.hpp
+++ b/bindings/cxx/internal.hpp
@@ -5,8 +5,7 @@
 #define __LIBGPIOD_CXX_INTERNAL_HPP__
 
 #include <gpiod.h>
-#include <iostream>
-#include <iterator>
+#include <map>
 #include <memory>
 #include <string>
 #include <utility>
@@ -25,6 +24,7 @@ map_int_to_enum(int value, const ::std::map<int, enum_type>& mapping)
 	try {
 		return mapping.at(value);
 	} catch (const ::std::out_of_range& err) {
+		/* FIXME Demangle the name. */
 		throw bad_mapping(::std::string("invalid value for ") +
 				  typeid(enum_type).name());
 	}
@@ -40,9 +40,11 @@ template<class T, void F(T*)> struct deleter
 	}
 };
 
+using chip_deleter = deleter<::gpiod_chip, ::gpiod_chip_close>;
 using chip_info_deleter = deleter<::gpiod_chip_info, ::gpiod_chip_info_free>;
 using line_info_deleter = deleter<::gpiod_line_info, ::gpiod_line_info_free>;
 using info_event_deleter = deleter<::gpiod_info_event, ::gpiod_info_event_free>;
+using line_settings_deleter = deleter<::gpiod_line_settings, ::gpiod_line_settings_free>;
 using line_config_deleter = deleter<::gpiod_line_config, ::gpiod_line_config_free>;
 using request_config_deleter = deleter<::gpiod_request_config, ::gpiod_request_config_free>;
 using line_request_deleter = deleter<::gpiod_line_request, ::gpiod_line_request_release>;
@@ -50,9 +52,11 @@ using edge_event_deleter = deleter<::gpiod_edge_event, ::gpiod_edge_event_free>;
 using edge_event_buffer_deleter = deleter<::gpiod_edge_event_buffer,
 					  ::gpiod_edge_event_buffer_free>;
 
+using chip_ptr = ::std::unique_ptr<::gpiod_chip, chip_deleter>;
 using chip_info_ptr = ::std::unique_ptr<::gpiod_chip_info, chip_info_deleter>;
 using line_info_ptr = ::std::unique_ptr<::gpiod_line_info, line_info_deleter>;
 using info_event_ptr = ::std::unique_ptr<::gpiod_info_event, info_event_deleter>;
+using line_settings_ptr = ::std::unique_ptr<::gpiod_line_settings, line_settings_deleter>;
 using line_config_ptr = ::std::unique_ptr<::gpiod_line_config, line_config_deleter>;
 using request_config_ptr = ::std::unique_ptr<::gpiod_request_config, request_config_deleter>;
 using line_request_ptr = ::std::unique_ptr<::gpiod_line_request, line_request_deleter>;
@@ -60,6 +64,19 @@ using edge_event_ptr = ::std::unique_ptr<::gpiod_edge_event, edge_event_deleter>
 using edge_event_buffer_ptr = ::std::unique_ptr<::gpiod_edge_event_buffer,
 						edge_event_buffer_deleter>;
 
+struct chip::impl
+{
+	impl(const ::std::filesystem::path& path);
+	impl(const impl& other) = delete;
+	impl(impl&& other) = delete;
+	impl& operator=(const impl& other) = delete;
+	impl& operator=(impl&& other) = delete;
+
+	void throw_if_closed() const;
+
+	chip_ptr chip;
+};
+
 struct chip_info::impl
 {
 	impl() = default;
@@ -100,6 +117,17 @@ struct info_event::impl
 	line_info info;
 };
 
+struct line_settings::impl
+{
+	impl();
+	impl(const impl& other) = delete;
+	impl(impl&& other) = delete;
+	impl& operator=(const impl& other) = delete;
+	impl& operator=(impl&& other) = delete;
+
+	line_settings_ptr settings;
+};
+
 struct line_config::impl
 {
 	impl();
diff --git a/bindings/cxx/line-config.cpp b/bindings/cxx/line-config.cpp
index 442b2b3..f7f1bfa 100644
--- a/bindings/cxx/line-config.cpp
+++ b/bindings/cxx/line-config.cpp
@@ -1,9 +1,12 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
+#include <cstdlib>
 #include <iterator>
-#include <map>
+#include <ostream>
 #include <sstream>
+#include <utility>
+#include <vector>
 
 #include "internal.hpp"
 
@@ -11,73 +14,6 @@ namespace gpiod {
 
 namespace {
 
-template<class enum_type>
-::std::map<int, enum_type> make_reverse_maping(const ::std::map<enum_type, int>& mapping)
-{
-	::std::map<int, enum_type> ret;
-
-	for (const auto &item: mapping)
-		ret[item.second] = item.first;
-
-	return ret;
-}
-
-const ::std::map<line::direction, int> direction_mapping = {
-	{ line::direction::AS_IS,	GPIOD_LINE_DIRECTION_AS_IS },
-	{ line::direction::INPUT,	GPIOD_LINE_DIRECTION_INPUT },
-	{ line::direction::OUTPUT,	GPIOD_LINE_DIRECTION_OUTPUT }
-};
-
-const ::std::map<int, line::direction> reverse_direction_mapping = make_reverse_maping(direction_mapping);
-
-const ::std::map<line::edge, int> edge_mapping = {
-	{ line::edge::NONE,		GPIOD_LINE_EDGE_NONE },
-	{ line::edge::FALLING,		GPIOD_LINE_EDGE_FALLING },
-	{ line::edge::RISING,		GPIOD_LINE_EDGE_RISING },
-	{ line::edge::BOTH,		GPIOD_LINE_EDGE_BOTH }
-};
-
-const ::std::map<int, line::edge> reverse_edge_mapping = make_reverse_maping(edge_mapping);
-
-const ::std::map<line::bias, int> bias_mapping = {
-	{ line::bias::AS_IS,		GPIOD_LINE_BIAS_AS_IS },
-	{ line::bias::DISABLED,		GPIOD_LINE_BIAS_DISABLED },
-	{ line::bias::PULL_UP,		GPIOD_LINE_BIAS_PULL_UP },
-	{ line::bias::PULL_DOWN,	GPIOD_LINE_BIAS_PULL_DOWN }
-};
-
-const ::std::map<int, line::bias> reverse_bias_mapping = make_reverse_maping(bias_mapping);
-
-const ::std::map<line::drive, int> drive_mapping = {
-	{ line::drive::PUSH_PULL,	GPIOD_LINE_DRIVE_PUSH_PULL },
-	{ line::drive::OPEN_DRAIN,	GPIOD_LINE_DRIVE_OPEN_DRAIN },
-	{ line::drive::OPEN_SOURCE,	GPIOD_LINE_DRIVE_OPEN_SOURCE }
-};
-
-const ::std::map<int, line::drive> reverse_drive_mapping = make_reverse_maping(drive_mapping);
-
-const ::std::map<line::clock, int> clock_mapping = {
-	{ line::clock::MONOTONIC,	GPIOD_LINE_EVENT_CLOCK_MONOTONIC },
-	{ line::clock::REALTIME,	GPIOD_LINE_EVENT_CLOCK_REALTIME },
-};
-
-const ::std::map<int, line::clock> reverse_clock_mapping = make_reverse_maping(clock_mapping);
-
-template<class key_type, class value_type, class exception_type>
-value_type map_setting(const key_type& key, const ::std::map<key_type, value_type>& mapping)
-{
-	value_type ret;
-
-	try {
-		ret = mapping.at(key);
-	} catch (const ::std::out_of_range& err) {
-		throw exception_type(::std::string("invalid value for ") +
-				     typeid(key_type).name());
-	}
-
-	return ret;
-}
-
 ::gpiod_line_config* make_line_config()
 {
 	::gpiod_line_config *config = ::gpiod_line_config_new();
@@ -87,57 +23,12 @@ value_type map_setting(const key_type& key, const ::std::map<key_type, value_typ
 	return config;
 }
 
-template<class enum_type>
-int do_map_value(enum_type value, const ::std::map<enum_type, int>& mapping)
+struct malloc_deleter
 {
-	return map_setting<enum_type, int, ::std::invalid_argument>(value, mapping);
-}
-
-template<class enum_type, void set_func(::gpiod_line_config*, int)>
-void set_mapped_value_default(::gpiod_line_config* config, enum_type value,
-			      const ::std::map<enum_type, int>& mapping)
-{
-	int mapped_val = do_map_value(value, mapping);
-
-	set_func(config, mapped_val);
-}
-
-template<class enum_type, void set_func(::gpiod_line_config*, int, unsigned int)>
-void set_mapped_value_override(::gpiod_line_config* config, enum_type value, line::offset offset,
-			       const ::std::map<enum_type, int>& mapping)
-{
-	int mapped_val = do_map_value(value, mapping);
-
-	set_func(config, mapped_val, offset);
-}
-
-template<class ret_type, int get_func(::gpiod_line_config*)>
-ret_type get_mapped_value_default(::gpiod_line_config* config,
-				  const ::std::map<int, ret_type>& mapping)
-{
-	int mapped_val = get_func(config);
-
-	return map_int_to_enum(mapped_val, mapping);
-}
-
-template<class ret_type, int get_func(::gpiod_line_config*, unsigned int)>
-ret_type get_mapped_value_offset(::gpiod_line_config* config, line::offset offset,
-				 const ::std::map<int, ret_type>& mapping)
-{
-	int mapped_val = get_func(config, offset);
-
-	return map_int_to_enum(mapped_val, mapping);
-}
-
-const ::std::map<int, line_config::property> property_mapping = {
-	{ GPIOD_LINE_CONFIG_PROP_DIRECTION,		line_config::property::DIRECTION },
-	{ GPIOD_LINE_CONFIG_PROP_EDGE_DETECTION,	line_config::property::EDGE_DETECTION },
-	{ GPIOD_LINE_CONFIG_PROP_BIAS,			line_config::property::BIAS },
-	{ GPIOD_LINE_CONFIG_PROP_DRIVE,			line_config::property::DRIVE },
-	{ GPIOD_LINE_CONFIG_PROP_ACTIVE_LOW,		line_config::property::ACTIVE_LOW },
-	{ GPIOD_LINE_CONFIG_PROP_DEBOUNCE_PERIOD_US,	line_config::property::DEBOUNCE_PERIOD },
-	{ GPIOD_LINE_CONFIG_PROP_EVENT_CLOCK,		line_config::property::EVENT_CLOCK },
-	{ GPIOD_LINE_CONFIG_PROP_OUTPUT_VALUE,		line_config::property::OUTPUT_VALUE }
+	void operator()(void* ptr)
+	{
+		::free(ptr);
+	}
 };
 
 } /* namespace */
@@ -148,15 +39,10 @@ line_config::impl::impl()
 
 }
 
-GPIOD_CXX_API line_config::line_config(const properties& props)
+GPIOD_CXX_API line_config::line_config()
 	: _m_priv(new impl)
 {
-	for (const auto& prop: props) {
-		if (prop.first == property::OUTPUT_VALUES)
-			this->set_output_values(::std::any_cast<line::value_mappings>(prop.second));
-		else
-			this->set_property_default(prop.first, prop.second);
-	}
+
 }
 
 GPIOD_CXX_API line_config::line_config(line_config&& other) noexcept
@@ -170,9 +56,11 @@ GPIOD_CXX_API line_config::~line_config()
 
 }
 
-GPIOD_CXX_API void line_config::reset() noexcept
+line_config& line_config::operator=(const line_config& other)
 {
-	::gpiod_line_config_reset(this->_m_priv->config.get());
+	this->_m_priv = other._m_priv;
+
+	return *this;
 }
 
 GPIOD_CXX_API line_config& line_config::operator=(line_config&& other) noexcept
@@ -182,502 +70,93 @@ GPIOD_CXX_API line_config& line_config::operator=(line_config&& other) noexcept
 	return *this;
 }
 
-GPIOD_CXX_API void line_config::set_property_default(property prop, const ::std::any& val)
-{
-	switch(prop) {
-	case property::DIRECTION:
-		this->set_direction_default(::std::any_cast<line::direction>(val));
-		break;
-	case property::EDGE_DETECTION:
-		this->set_edge_detection_default(::std::any_cast<line::edge>(val));
-		break;
-	case property::BIAS:
-		this->set_bias_default(::std::any_cast<line::bias>(val));
-		break;
-	case property::DRIVE:
-		this->set_drive_default(::std::any_cast<line::drive>(val));
-		break;
-	case property::ACTIVE_LOW:
-		this->set_active_low_default(::std::any_cast<bool>(val));
-		break;
-	case property::DEBOUNCE_PERIOD:
-		this->set_debounce_period_default(::std::any_cast<::std::chrono::microseconds>(val));
-		break;
-	case property::EVENT_CLOCK:
-		this->set_event_clock_default(::std::any_cast<line::clock>(val));
-		break;
-	case property::OUTPUT_VALUE:
-		this->set_output_value_default(::std::any_cast<line::value>(val));
-		break;
-	default:
-		throw ::std::invalid_argument("invalid property type");
-	}
-}
-
-GPIOD_CXX_API void line_config::set_property_offset(property prop, line::offset offset,
-						    const ::std::any& val)
-{
-	switch(prop) {
-	case property::DIRECTION:
-		this->set_direction_override(::std::any_cast<line::direction>(val), offset);
-		break;
-	case property::EDGE_DETECTION:
-		this->set_edge_detection_override(::std::any_cast<line::edge>(val), offset);
-		break;
-	case property::BIAS:
-		this->set_bias_override(::std::any_cast<line::bias>(val), offset);
-		break;
-	case property::DRIVE:
-		this->set_drive_override(::std::any_cast<line::drive>(val), offset);
-		break;
-	case property::ACTIVE_LOW:
-		this->set_active_low_override(::std::any_cast<bool>(val), offset);
-		break;
-	case property::DEBOUNCE_PERIOD:
-		this->set_debounce_period_override(::std::any_cast<::std::chrono::microseconds>(val),
-						      offset);
-		break;
-	case property::EVENT_CLOCK:
-		this->set_event_clock_override(::std::any_cast<line::clock>(val), offset);
-		break;
-	case property::OUTPUT_VALUE:
-		this->set_output_value_override(::std::any_cast<line::value>(val), offset);
-		break;
-	default:
-		throw ::std::invalid_argument("invalid property type");
-	}
-}
-
-GPIOD_CXX_API void line_config::set_direction_default(line::direction direction)
-{
-	set_mapped_value_default<line::direction,
-				 ::gpiod_line_config_set_direction_default>(this->_m_priv->config.get(),
-									    direction, direction_mapping);
-}
-
-GPIOD_CXX_API void line_config::set_direction_override(line::direction direction, line::offset offset)
-{
-	set_mapped_value_override<line::direction,
-				  ::gpiod_line_config_set_direction_override>(this->_m_priv->config.get(),
-									      direction, offset,
-									      direction_mapping);
-}
-
-GPIOD_CXX_API line::direction line_config::direction_default() const
-{
-	return get_mapped_value_default<line::direction,
-					::gpiod_line_config_get_direction_default>(
-							this->_m_priv->config.get(),
-							reverse_direction_mapping);
-}
-
-GPIOD_CXX_API line::direction line_config::direction_offset(line::offset offset) const
-{
-	return get_mapped_value_offset<line::direction,
-				       ::gpiod_line_config_get_direction_offset>(
-						       this->_m_priv->config.get(),
-						       offset, reverse_direction_mapping);
-}
-
-GPIOD_CXX_API void line_config::clear_direction_override(line::offset offset) noexcept
-{
-	::gpiod_line_config_clear_direction_override(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API bool line_config::direction_is_overridden(line::offset offset) const noexcept
-{
-	return ::gpiod_line_config_direction_is_overridden(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API void line_config::set_edge_detection_default(line::edge edge)
-{
-	set_mapped_value_default<line::edge,
-				 ::gpiod_line_config_set_edge_detection_default>(
-						 this->_m_priv->config.get(),
-						 edge, edge_mapping);
-}
-
-GPIOD_CXX_API void line_config::set_edge_detection_override(line::edge edge, line::offset offset)
-{
-	set_mapped_value_override<line::edge,
-				  ::gpiod_line_config_set_edge_detection_override>(
-						this->_m_priv->config.get(),
-						edge, offset, edge_mapping);
-}
-
-GPIOD_CXX_API line::edge line_config::edge_detection_default() const
-{
-	return get_mapped_value_default<line::edge,
-					::gpiod_line_config_get_edge_detection_default>(
-							this->_m_priv->config.get(),
-							reverse_edge_mapping);
-}
-
-GPIOD_CXX_API line::edge line_config::edge_detection_offset(line::offset offset) const
-{
-	return get_mapped_value_offset<line::edge,
-				       ::gpiod_line_config_get_edge_detection_offset>(
-						       this->_m_priv->config.get(),
-						       offset, reverse_edge_mapping);
-}
-
-GPIOD_CXX_API void line_config::clear_edge_detection_override(line::offset offset) noexcept
-{
-	::gpiod_line_config_clear_edge_detection_override(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API bool line_config::edge_detection_is_overridden(line::offset offset) const noexcept
+GPIOD_CXX_API line_config& line_config::reset() noexcept
 {
-	return ::gpiod_line_config_edge_detection_is_overridden(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API void line_config::set_bias_default(line::bias bias)
-{
-	set_mapped_value_default<line::bias,
-				 ::gpiod_line_config_set_bias_default>(this->_m_priv->config.get(),
-								       bias, bias_mapping);
-}
-
-GPIOD_CXX_API void line_config::set_bias_override(line::bias bias, line::offset offset)
-{
-	set_mapped_value_override<line::bias,
-				 ::gpiod_line_config_set_bias_override>(this->_m_priv->config.get(),
-									bias, offset, bias_mapping);
-}
-
-GPIOD_CXX_API line::bias line_config::bias_default() const
-{
-	return get_mapped_value_default<line::bias,
-					::gpiod_line_config_get_bias_default>(this->_m_priv->config.get(),
-									      reverse_bias_mapping);
-}
-
-GPIOD_CXX_API line::bias line_config::bias_offset(line::offset offset) const
-{
-	return get_mapped_value_offset<line::bias,
-				       ::gpiod_line_config_get_bias_offset>(this->_m_priv->config.get(),
-									    offset, reverse_bias_mapping);
-}
-
-GPIOD_CXX_API void line_config::clear_bias_override(line::offset offset) noexcept
-{
-	::gpiod_line_config_clear_bias_override(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API bool line_config::bias_is_overridden(line::offset offset) const noexcept
-{
-	return ::gpiod_line_config_bias_is_overridden(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API void line_config::set_drive_default(line::drive drive)
-{
-	set_mapped_value_default<line::drive,
-				 ::gpiod_line_config_set_drive_default>(this->_m_priv->config.get(),
-									drive, drive_mapping);
-}
-
-GPIOD_CXX_API void line_config::set_drive_override(line::drive drive, line::offset offset)
-{
-	set_mapped_value_override<line::drive,
-				  ::gpiod_line_config_set_drive_override>(this->_m_priv->config.get(),
-									  drive, offset, drive_mapping);
-}
-
-GPIOD_CXX_API line::drive line_config::drive_default() const
-{
-	return get_mapped_value_default<line::drive,
-					::gpiod_line_config_get_drive_default>(this->_m_priv->config.get(),
-									       reverse_drive_mapping);
-}
-
-GPIOD_CXX_API line::drive line_config::drive_offset(line::offset offset) const
-{
-	return get_mapped_value_offset<line::drive,
-				       ::gpiod_line_config_get_drive_offset>(this->_m_priv->config.get(),
-									     offset, reverse_drive_mapping);
-}
-
-GPIOD_CXX_API void line_config::clear_drive_override(line::offset offset) noexcept
-{
-	::gpiod_line_config_clear_drive_override(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API bool line_config::drive_is_overridden(line::offset offset) const noexcept
-{
-	return ::gpiod_line_config_drive_is_overridden(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API void line_config::set_active_low_default(bool active_low) noexcept
-{
-	::gpiod_line_config_set_active_low_default(this->_m_priv->config.get(), active_low);
-}
-
-GPIOD_CXX_API void line_config::set_active_low_override(bool active_low, line::offset offset) noexcept
-{
-	::gpiod_line_config_set_active_low_override(this->_m_priv->config.get(), active_low, offset);
-}
+	::gpiod_line_config_reset(this->_m_priv->config.get());
 
-GPIOD_CXX_API bool line_config::active_low_default() const noexcept
-{
-	return ::gpiod_line_config_get_active_low_default(this->_m_priv->config.get());
+	return *this;
 }
 
-GPIOD_CXX_API bool line_config::active_low_offset(line::offset offset) const noexcept
+GPIOD_CXX_API line_config& line_config::add_line_settings(line::offset offset,
+							  const line_settings& settings)
 {
-	return ::gpiod_line_config_get_active_low_offset(this->_m_priv->config.get(), offset);
+	return this->add_line_settings(line::offsets({offset}), settings);
 }
 
-GPIOD_CXX_API void line_config::clear_active_low_override(line::offset offset) noexcept
+GPIOD_CXX_API line_config& line_config::add_line_settings(const line::offsets& offsets,
+							  const line_settings& settings)
 {
-	::gpiod_line_config_clear_active_low_override(this->_m_priv->config.get(), offset);
-}
+	::std::vector<unsigned int> raw_offsets(offsets.size());
 
-GPIOD_CXX_API bool line_config::active_low_is_overridden(line::offset offset) const noexcept
-{
-	return ::gpiod_line_config_active_low_is_overridden(this->_m_priv->config.get(), offset);
-}
+	for (unsigned int i = 0; i < offsets.size(); i++)
+		raw_offsets[i] = offsets[i];
 
-GPIOD_CXX_API void
-line_config::set_debounce_period_default(const ::std::chrono::microseconds& period) noexcept
-{
-	::gpiod_line_config_set_debounce_period_us_default(this->_m_priv->config.get(), period.count());
-}
+	auto ret = ::gpiod_line_config_add_line_settings(this->_m_priv->config.get(),
+							 raw_offsets.data(), raw_offsets.size(),
+							 settings._m_priv->settings.get());
+	if (ret)
+		throw_from_errno("unable to add line settings");
 
-GPIOD_CXX_API void
-line_config::set_debounce_period_override(const ::std::chrono::microseconds& period,
-					     line::offset offset) noexcept
-{
-	::gpiod_line_config_set_debounce_period_us_override(this->_m_priv->config.get(),
-							    period.count(), offset);
+	return *this;
 }
 
-GPIOD_CXX_API ::std::chrono::microseconds line_config::debounce_period_default() const noexcept
+GPIOD_CXX_API ::std::map<line::offset, line_settings> line_config::get_line_settings() const
 {
-	return ::std::chrono::microseconds(
-			::gpiod_line_config_get_debounce_period_us_default(this->_m_priv->config.get()));
-}
+	::std::map<line::offset, line_settings> settings_map;
+	::std::size_t num_offsets;
+	unsigned int *offsets_ptr;
+	int ret;
 
-GPIOD_CXX_API ::std::chrono::microseconds
-line_config::debounce_period_offset(line::offset offset) const noexcept
-{
-	return ::std::chrono::microseconds(
-			::gpiod_line_config_get_debounce_period_us_offset(this->_m_priv->config.get(),
-									  offset));
-}
+	ret = ::gpiod_line_config_get_offsets(this->_m_priv->config.get(),
+					      &num_offsets, &offsets_ptr);
+	if (ret)
+		throw_from_errno("unable to retrieve line offsets");
 
-GPIOD_CXX_API void line_config::clear_debounce_period_override(line::offset offset) noexcept
-{
-	::gpiod_line_config_clear_debounce_period_us_override(this->_m_priv->config.get(), offset);
-}
+	if (num_offsets == 0)
+		return settings_map;
 
-GPIOD_CXX_API bool line_config::debounce_period_is_overridden(line::offset offset) const noexcept
-{
-	return ::gpiod_line_config_debounce_period_us_is_overridden(this->_m_priv->config.get(), offset);
-}
+	::std::unique_ptr<unsigned int, malloc_deleter> offsets(offsets_ptr);
 
-GPIOD_CXX_API void line_config::set_event_clock_default(line::clock clock)
-{
-	set_mapped_value_default<line::clock,
-				 ::gpiod_line_config_set_event_clock_default>(this->_m_priv->config.get(),
-									      clock, clock_mapping);
-}
+	for (size_t i = 0; i < num_offsets; i++) {
+		line_settings settings;
 
-GPIOD_CXX_API void line_config::set_event_clock_override(line::clock clock, line::offset offset)
-{
-	set_mapped_value_override<line::clock,
-				  ::gpiod_line_config_set_event_clock_override>(this->_m_priv->config.get(),
-										clock, offset,
-										clock_mapping);
-}
-
-GPIOD_CXX_API line::clock line_config::event_clock_default() const
-{
-	return get_mapped_value_default<line::clock,
-					::gpiod_line_config_get_event_clock_default>(
+		settings._m_priv->settings.reset(::gpiod_line_config_get_line_settings(
 							this->_m_priv->config.get(),
-							reverse_clock_mapping);
-}
-
-GPIOD_CXX_API line::clock line_config::event_clock_offset(line::offset offset) const
-{
-	return get_mapped_value_offset<line::clock,
-					::gpiod_line_config_get_event_clock_offset>(
-							this->_m_priv->config.get(),
-							offset, reverse_clock_mapping);
-}
-
-GPIOD_CXX_API void line_config::clear_event_clock_override(line::offset offset) noexcept
-{
-	::gpiod_line_config_clear_event_clock_override(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API bool line_config::event_clock_is_overridden(line::offset offset) const noexcept
-{
-	return ::gpiod_line_config_event_clock_is_overridden(this->_m_priv->config.get(), offset);
-}
+							offsets.get()[i]));
+		if (!settings._m_priv->settings)
+			throw_from_errno("unable to retrieve line settings");
 
-GPIOD_CXX_API void line_config::set_output_value_default(line::value value) noexcept
-{
-	::gpiod_line_config_set_output_value_default(this->_m_priv->config.get(), static_cast<int>(value));
-}
-
-GPIOD_CXX_API void line_config::set_output_value_override(line::value value, line::offset offset) noexcept
-{
-	::gpiod_line_config_set_output_value_override(this->_m_priv->config.get(),
-						      static_cast<int>(value), offset);
-}
-
-GPIOD_CXX_API void line_config::set_output_values(const line::value_mappings& values)
-{
-	line::offsets offsets;
-	line::values vals;
-
-	if (values.empty())
-		return;
-
-	offsets.reserve(values.size());
-	vals.reserve(values.size());
-
-	for (auto& val: values) {
-		offsets.push_back(val.first);
-		vals.push_back(val.second);
+		settings_map[offsets.get()[i]] = ::std::move(settings);
 	}
 
-	this->set_output_values(offsets, vals);
+	return settings_map;
 }
 
-GPIOD_CXX_API void line_config::set_output_values(const line::offsets& offsets,
-						  const line::values& values)
+GPIOD_CXX_API ::std::ostream&
+operator<<(::std::ostream& out, const line_config& config)
 {
-	if (offsets.size() != values.size())
-		throw ::std::invalid_argument("values must have the same size as the offsets");
-
-	if (offsets.empty())
-		return;
+	auto settings_map = config.get_line_settings();
+	::std::vector<::std::string> vec;
 
-	::std::vector<unsigned int> buf(offsets.size());
+	out << "gpiod::line_config(num_settings=" << settings_map.size();
 
-	for (unsigned int i = 0; i < offsets.size(); i++)
-		buf[i] = offsets[i];
-
-	::gpiod_line_config_set_output_values(this->_m_priv->config.get(),
-					      offsets.size(), buf.data(),
-					      reinterpret_cast<const int*>(values.data()));
-}
-
-GPIOD_CXX_API line::value line_config::output_value_default() const noexcept
-{
-	return static_cast<line::value>(::gpiod_line_config_get_output_value_default(
-								this->_m_priv->config.get()));
-}
-
-GPIOD_CXX_API line::value line_config::output_value_offset(line::offset offset) const noexcept
-{
-	return static_cast<line::value>(
-			::gpiod_line_config_get_output_value_offset(this->_m_priv->config.get(),
-								    offset));
-}
-
-GPIOD_CXX_API void line_config::clear_output_value_override(line::offset offset) noexcept
-{
-	::gpiod_line_config_clear_output_value_override(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API bool line_config::output_value_is_overridden(line::offset offset) const noexcept
-{
-	return ::gpiod_line_config_output_value_is_overridden(this->_m_priv->config.get(), offset);
-}
-
-GPIOD_CXX_API ::std::size_t line_config::num_overrides() const noexcept
-{
-	return ::gpiod_line_config_get_num_overrides(this->_m_priv->config.get());
-}
-
-GPIOD_CXX_API line_config::override_list line_config::overrides() const
-{
-	unsigned int num_overrides = this->num_overrides();
-	override_list ret(num_overrides);
-	::std::vector<unsigned int> offsets(num_overrides);
-	::std::vector<int> props(num_overrides);
-
-	::gpiod_line_config_get_overrides(this->_m_priv->config.get(), offsets.data(), props.data());
-
-	for (unsigned int i = 0; i < num_overrides; i++)
-		ret[i] = { offsets[i], property_mapping.at(props[i]) };
+	if (settings_map.size() == 0) {
+		out << ")";
+		return out;
+	}
 
-	return ret;
-}
+	for (const auto& [offset, settings]: settings_map) {
+		::std::stringstream str;
 
-GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_config& config)
-{
-	out << "gpiod::line_config(defaults=(direction=" << config.direction_default() <<
-	       ", edge_detection=" << config.edge_detection_default() <<
-	       ", bias=" << config.bias_default() <<
-	       ", drive=" << config.drive_default() << ", " <<
-	       (config.active_low_default() ? "active-low" : "active-high") <<
-	       ", debounce_period=" << config.debounce_period_default().count() << "us" <<
-	       ", event_clock=" << config.event_clock_default() <<
-	       ", default_output_value=" << config.output_value_default() <<
-	       "), ";
-
-	if (config.num_overrides()) {
-		::std::vector<::std::string> overrides(config.num_overrides());
-		::std::vector<::std::string>::iterator it = overrides.begin();
-
-		out << "overrides=[";
-
-		for (const auto& override: config.overrides()) {
-			line::offset offset = override.first;
-			line_config::property prop = override.second;
-			::std::stringstream out;
-
-			out << "(offset=" << offset << " -> ";
-
-			switch (prop) {
-			case line_config::property::DIRECTION:
-				out << "direction=" << config.direction_offset(offset);
-				break;
-			case line_config::property::EDGE_DETECTION:
-				out << "edge_detection=" << config.edge_detection_offset(offset);
-				break;
-			case line_config::property::BIAS:
-				out << "bias=" << config.bias_offset(offset);
-				break;
-			case line_config::property::DRIVE:
-				out << "drive=" << config.drive_offset(offset);
-				break;
-			case line_config::property::ACTIVE_LOW:
-				out << (config.active_low_offset(offset) ? "active-low" : "active-high");
-				break;
-			case line_config::property::DEBOUNCE_PERIOD:
-				out << "debounce_period=" <<
-				       config.debounce_period_offset(offset).count() << "us";
-				break;
-			case line_config::property::EVENT_CLOCK:
-				out << "event_clock=" << config.event_clock_offset(offset);
-				break;
-			case line_config::property::OUTPUT_VALUE:
-				out << "output_value=" << config.output_value_offset(offset);
-				break;
-			default:
-				/* OUTPUT_VALUES is ignored. */
-				break;
-			}
-
-			out << ")";
-
-			*it = out.str();
-			it++;
-		}
-
-		::std::copy(overrides.begin(), ::std::prev(overrides.end()),
-			    ::std::ostream_iterator<::std::string>(out, ", "));
-		out << overrides.back();
-
-		out << "]";
+		str << offset << ": " << settings;
+		vec.push_back(str.str());
 	}
 
-	out << ")";
+	out << ", settings=[";
+	::std::copy(vec.begin(), ::std::prev(vec.end()),
+		    ::std::ostream_iterator<::std::string>(out, ", "));
+	out << vec.back();
+	out << "])";
 
 	return out;
 }
diff --git a/bindings/cxx/line-info.cpp b/bindings/cxx/line-info.cpp
index 66f3242..2ad0baf 100644
--- a/bindings/cxx/line-info.cpp
+++ b/bindings/cxx/line-info.cpp
@@ -2,7 +2,8 @@
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
 #include <map>
-#include <iostream>
+#include <ostream>
+#include <utility>
 
 #include "internal.hpp"
 
diff --git a/bindings/cxx/line-request.cpp b/bindings/cxx/line-request.cpp
index 58777f3..bde34e8 100644
--- a/bindings/cxx/line-request.cpp
+++ b/bindings/cxx/line-request.cpp
@@ -2,6 +2,7 @@
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
 #include <iterator>
+#include <ostream>
 #include <utility>
 
 #include "internal.hpp"
@@ -126,12 +127,13 @@ GPIOD_CXX_API void line_request::get_values(line::values& values)
 	this->get_values(this->offsets(), values);
 }
 
-GPIOD_CXX_API void line_request::line_request::set_value(line::offset offset, line::value value)
+GPIOD_CXX_API line_request&
+line_request::line_request::set_value(line::offset offset, line::value value)
 {
-	this->set_values({ offset }, { value });
+	return this->set_values({ offset }, { value });
 }
 
-GPIOD_CXX_API void
+GPIOD_CXX_API line_request&
 line_request::set_values(const line::value_mappings& values)
 {
 	line::offsets offsets(values.size());
@@ -142,10 +144,10 @@ line_request::set_values(const line::value_mappings& values)
 		vals[i] = values[i].second;
 	}
 
-	this->set_values(offsets, vals);
+	return this->set_values(offsets, vals);
 }
 
-GPIOD_CXX_API void line_request::set_values(const line::offsets& offsets,
+GPIOD_CXX_API line_request& line_request::set_values(const line::offsets& offsets,
 					    const line::values& values)
 {
 	this->_m_priv->throw_if_released();
@@ -160,14 +162,16 @@ GPIOD_CXX_API void line_request::set_values(const line::offsets& offsets,
 							 reinterpret_cast<const int*>(values.data()));
 	if (ret)
 		throw_from_errno("unable to set line values");
+
+	return *this;
 }
 
-GPIOD_CXX_API void line_request::set_values(const line::values& values)
+GPIOD_CXX_API line_request& line_request::set_values(const line::values& values)
 {
-	this->set_values(this->offsets(), values);
+	return this->set_values(this->offsets(), values);
 }
 
-GPIOD_CXX_API void line_request::reconfigure_lines(const line_config& config)
+GPIOD_CXX_API line_request& line_request::reconfigure_lines(const line_config& config)
 {
 	this->_m_priv->throw_if_released();
 
@@ -175,6 +179,8 @@ GPIOD_CXX_API void line_request::reconfigure_lines(const line_config& config)
 							 config._m_priv->config.get());
 	if (ret)
 		throw_from_errno("unable to reconfigure GPIO lines");
+
+	return *this;
 }
 
 GPIOD_CXX_API int line_request::fd() const
diff --git a/bindings/cxx/line-settings.cpp b/bindings/cxx/line-settings.cpp
new file mode 100644
index 0000000..dbbe30e
--- /dev/null
+++ b/bindings/cxx/line-settings.cpp
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+template<class enum_type>
+::std::map<int, enum_type> make_reverse_maping(const ::std::map<enum_type, int>& mapping)
+{
+	::std::map<int, enum_type> ret;
+
+	for (const auto &item: mapping)
+		ret[item.second] = item.first;
+
+	return ret;
+}
+
+const ::std::map<line::direction, int> direction_mapping = {
+	{ line::direction::AS_IS,	GPIOD_LINE_DIRECTION_AS_IS },
+	{ line::direction::INPUT,	GPIOD_LINE_DIRECTION_INPUT },
+	{ line::direction::OUTPUT,	GPIOD_LINE_DIRECTION_OUTPUT }
+};
+
+const ::std::map<int, line::direction> reverse_direction_mapping = make_reverse_maping(direction_mapping);
+
+const ::std::map<line::edge, int> edge_mapping = {
+	{ line::edge::NONE,		GPIOD_LINE_EDGE_NONE },
+	{ line::edge::FALLING,		GPIOD_LINE_EDGE_FALLING },
+	{ line::edge::RISING,		GPIOD_LINE_EDGE_RISING },
+	{ line::edge::BOTH,		GPIOD_LINE_EDGE_BOTH }
+};
+
+const ::std::map<int, line::edge> reverse_edge_mapping = make_reverse_maping(edge_mapping);
+
+const ::std::map<line::bias, int> bias_mapping = {
+	{ line::bias::AS_IS,		GPIOD_LINE_BIAS_AS_IS },
+	{ line::bias::DISABLED,		GPIOD_LINE_BIAS_DISABLED },
+	{ line::bias::PULL_UP,		GPIOD_LINE_BIAS_PULL_UP },
+	{ line::bias::PULL_DOWN,	GPIOD_LINE_BIAS_PULL_DOWN }
+};
+
+const ::std::map<int, line::bias> reverse_bias_mapping = make_reverse_maping(bias_mapping);
+
+const ::std::map<line::drive, int> drive_mapping = {
+	{ line::drive::PUSH_PULL,	GPIOD_LINE_DRIVE_PUSH_PULL },
+	{ line::drive::OPEN_DRAIN,	GPIOD_LINE_DRIVE_OPEN_DRAIN },
+	{ line::drive::OPEN_SOURCE,	GPIOD_LINE_DRIVE_OPEN_SOURCE }
+};
+
+const ::std::map<int, line::drive> reverse_drive_mapping = make_reverse_maping(drive_mapping);
+
+const ::std::map<line::clock, int> clock_mapping = {
+	{ line::clock::MONOTONIC,	GPIOD_LINE_EVENT_CLOCK_MONOTONIC },
+	{ line::clock::REALTIME,	GPIOD_LINE_EVENT_CLOCK_REALTIME }
+};
+
+const ::std::map<int, line::clock> reverse_clock_mapping = make_reverse_maping(clock_mapping);
+
+const ::std::map<line::value, int> value_mapping = {
+	{ line::value::INACTIVE,	GPIOD_LINE_VALUE_INACTIVE },
+	{ line::value::ACTIVE,		GPIOD_LINE_VALUE_ACTIVE }
+};
+
+const ::std::map<int, line::value> reverse_value_mapping = make_reverse_maping(value_mapping);
+
+line_settings_ptr make_line_settings()
+{
+	line_settings_ptr settings(::gpiod_line_settings_new());
+	if (!settings)
+		throw_from_errno("Unable to allocate the line settings object");
+
+	return settings;
+}
+
+template<class key_type, class value_type, class exception_type>
+value_type map_setting(const key_type& key, const ::std::map<key_type, value_type>& mapping)
+{
+	value_type ret;
+
+	try {
+		ret = mapping.at(key);
+	} catch (const ::std::out_of_range& err) {
+		/* FIXME Demangle the name. */
+		throw exception_type(::std::string("invalid value for ") +
+				     typeid(key_type).name());
+	}
+
+	return ret;
+}
+
+template<class enum_type>
+int do_map_value(enum_type value, const ::std::map<enum_type, int>& mapping)
+{
+	return map_setting<enum_type, int, ::std::invalid_argument>(value, mapping);
+}
+
+template<class enum_type, int set_func(::gpiod_line_settings*, int)>
+void set_mapped_value(::gpiod_line_settings* settings, enum_type value,
+		      const ::std::map<enum_type, int>& mapping)
+{
+	auto mapped_val = do_map_value(value, mapping);
+
+	auto ret = set_func(settings, mapped_val);
+	if (ret)
+		throw_from_errno("unable to set property");
+}
+
+template<class ret_type, int get_func(::gpiod_line_settings*)>
+ret_type get_mapped_value(::gpiod_line_settings* settings,
+			  const ::std::map<int, ret_type>& mapping)
+{
+	int mapped_val = get_func(settings);
+
+	return map_int_to_enum(mapped_val, mapping);
+}
+
+} /* namespace */
+
+line_settings::impl::impl()
+	: settings(make_line_settings())
+{
+
+}
+
+GPIOD_CXX_API line_settings::line_settings()
+	: _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_settings::line_settings(line_settings&& other) noexcept
+	: _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_settings::~line_settings()
+{
+
+}
+
+GPIOD_CXX_API line_settings& line_settings::operator=(line_settings&& other)
+{
+	this->_m_priv = ::std::move(other._m_priv);
+
+	return *this;
+}
+
+GPIOD_CXX_API line_settings& line_settings::reset(void) noexcept
+{
+	::gpiod_line_settings_reset(this->_m_priv->settings.get());
+
+	return *this;
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_direction(line::direction direction)
+{
+	set_mapped_value<line::direction,
+			 ::gpiod_line_settings_set_direction>(this->_m_priv->settings.get(),
+							      direction, direction_mapping);
+
+	return *this;
+}
+
+GPIOD_CXX_API line::direction line_settings::direction() const
+{
+	return get_mapped_value<line::direction,
+				::gpiod_line_settings_get_direction>(
+							this->_m_priv->settings.get(),
+							reverse_direction_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_edge_detection(line::edge edge)
+{
+	set_mapped_value<line::edge,
+			 ::gpiod_line_settings_set_edge_detection>(this->_m_priv->settings.get(),
+								   edge, edge_mapping);
+
+	return *this;
+}
+
+GPIOD_CXX_API line::edge line_settings::edge_detection() const
+{
+	return get_mapped_value<line::edge,
+				::gpiod_line_settings_get_edge_detection>(
+							this->_m_priv->settings.get(),
+							reverse_edge_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_bias(line::bias bias)
+{
+	set_mapped_value<line::bias,
+			 ::gpiod_line_settings_set_bias>(this->_m_priv->settings.get(),
+							 bias, bias_mapping);
+
+	return *this;
+}
+
+GPIOD_CXX_API line::bias line_settings::bias() const
+{
+	return get_mapped_value<line::bias,
+				::gpiod_line_settings_get_bias>(this->_m_priv->settings.get(),
+								reverse_bias_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_drive(line::drive drive)
+{
+	set_mapped_value<line::drive,
+			 ::gpiod_line_settings_set_drive>(this->_m_priv->settings.get(),
+							  drive, drive_mapping);
+
+	return *this;
+}
+
+GPIOD_CXX_API line::drive line_settings::drive() const
+{
+	return get_mapped_value<line::drive,
+				::gpiod_line_settings_get_drive>(this->_m_priv->settings.get(),
+								 reverse_drive_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_active_low(bool active_low)
+{
+	::gpiod_line_settings_set_active_low(this->_m_priv->settings.get(), active_low);
+
+	return *this;
+}
+
+GPIOD_CXX_API bool line_settings::active_low() const noexcept
+{
+	return ::gpiod_line_settings_get_active_low(this->_m_priv->settings.get());
+}
+
+GPIOD_CXX_API line_settings&
+line_settings::set_debounce_period(const ::std::chrono::microseconds& period)
+{
+	::gpiod_line_settings_set_debounce_period_us(this->_m_priv->settings.get(), period.count());
+
+	return *this;
+}
+
+GPIOD_CXX_API ::std::chrono::microseconds line_settings::debounce_period() const noexcept
+{
+	return ::std::chrono::microseconds(
+			::gpiod_line_settings_get_debounce_period_us(this->_m_priv->settings.get()));
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_event_clock(line::clock event_clock)
+{
+	set_mapped_value<line::clock,
+			 ::gpiod_line_settings_set_event_clock>(this->_m_priv->settings.get(),
+								event_clock, clock_mapping);
+
+	return *this;
+}
+
+GPIOD_CXX_API line::clock line_settings::event_clock() const
+{
+	return get_mapped_value<line::clock,
+				::gpiod_line_settings_get_event_clock>(
+							this->_m_priv->settings.get(),
+							reverse_clock_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_output_value(line::value value)
+{
+	set_mapped_value<line::value,
+			 ::gpiod_line_settings_set_output_value>(this->_m_priv->settings.get(),
+								 value, value_mapping);
+
+	return *this;
+}
+
+GPIOD_CXX_API line::value line_settings::output_value() const
+{
+	return get_mapped_value<line::value,
+				::gpiod_line_settings_get_output_value>(
+							this->_m_priv->settings.get(),
+							reverse_value_mapping);
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_settings& settings)
+{
+	out << "gpiod::line_settings(direction=" << settings.direction() <<
+	       ", edge_detection=" << settings.edge_detection() <<
+	       ", bias=" << settings.bias() <<
+	       ", drive=" << settings.drive() <<
+	       ", " << (settings.active_low() ? "active-low" : "active-high") <<
+	       ", debounce_period=" << settings.debounce_period().count() <<
+	       ", event_clock=" << settings.event_clock() <<
+	       ", output_value=" << settings.output_value() <<
+	       ")";
+
+	return out;
+}
+
+} /* namespace gpiod */
diff --git a/bindings/cxx/line.cpp b/bindings/cxx/line.cpp
index fc20c62..a9caedd 100644
--- a/bindings/cxx/line.cpp
+++ b/bindings/cxx/line.cpp
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
 // SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
+#include <iterator>
+#include <ostream>
+
 #include "internal.hpp"
 
 namespace gpiod {
diff --git a/bindings/cxx/request-builder.cpp b/bindings/cxx/request-builder.cpp
new file mode 100644
index 0000000..6a1a487
--- /dev/null
+++ b/bindings/cxx/request-builder.cpp
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+struct request_builder::impl
+{
+	impl(chip& parent)
+		: line_cfg(),
+		  req_cfg(),
+		  parent(parent)
+	{
+
+	}
+
+	impl(const impl& other) = delete;
+	impl(impl&& other) = delete;
+	impl& operator=(const impl& other) = delete;
+	impl& operator=(impl&& other) = delete;
+
+	line_config line_cfg;
+	request_config req_cfg;
+	chip parent;
+};
+
+GPIOD_CXX_API request_builder::request_builder(chip& chip)
+	: _m_priv(new impl(chip))
+{
+
+}
+
+GPIOD_CXX_API request_builder::request_builder(request_builder&& other) noexcept
+	: _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API request_builder::~request_builder()
+{
+
+}
+
+GPIOD_CXX_API request_builder& request_builder::operator=(request_builder&& other) noexcept
+{
+	this->_m_priv = ::std::move(other._m_priv);
+
+	return *this;
+}
+
+GPIOD_CXX_API request_builder& request_builder::set_request_config(request_config& req_cfg)
+{
+	this->_m_priv->req_cfg = req_cfg;
+
+	return *this;
+}
+
+GPIOD_CXX_API const request_config& request_builder::get_request_config() const noexcept
+{
+	return this->_m_priv->req_cfg;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::set_consumer(const ::std::string& consumer) noexcept
+{
+	this->_m_priv->req_cfg.set_consumer(consumer);
+
+	return *this;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::set_event_buffer_size(::std::size_t event_buffer_size) noexcept
+{
+	this->_m_priv->req_cfg.set_event_buffer_size(event_buffer_size);
+
+	return *this;
+}
+
+GPIOD_CXX_API request_builder& request_builder::set_line_config(line_config &line_cfg)
+{
+	this->_m_priv->line_cfg = line_cfg;
+
+	return *this;
+}
+
+GPIOD_CXX_API const line_config& request_builder::get_line_config() const noexcept
+{
+	return this->_m_priv->line_cfg;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::add_line_settings(line::offset offset, const line_settings& settings)
+{
+	return this->add_line_settings(line::offsets({offset}), settings);
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::add_line_settings(const line::offsets& offsets, const line_settings& settings)
+{
+	this->_m_priv->line_cfg.add_line_settings(offsets, settings);
+
+	return *this;
+}
+
+GPIOD_CXX_API line_request request_builder::do_request()
+{
+	line_request_ptr request(::gpiod_chip_request_lines(
+					this->_m_priv->parent._m_priv->chip.get(),
+					this->_m_priv->req_cfg._m_priv->config.get(),
+					this->_m_priv->line_cfg._m_priv->config.get()));
+	if (!request)
+		throw_from_errno("error requesting GPIO lines");
+
+	line_request ret;
+	ret._m_priv.get()->set_request_ptr(request);
+
+	return ret;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const request_builder& builder)
+{
+	out << "gpiod::request_builder(request_config=" << builder._m_priv->req_cfg <<
+	       ", line_config=" << builder._m_priv->line_cfg <<
+	       ", parent=" << builder._m_priv->parent <<
+	       ")";
+
+	return out;
+}
+
+} /* namespace gpiod */
diff --git a/bindings/cxx/request-config.cpp b/bindings/cxx/request-config.cpp
index 9173238..e578b15 100644
--- a/bindings/cxx/request-config.cpp
+++ b/bindings/cxx/request-config.cpp
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
 // SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
 
+#include <ostream>
 #include <utility>
 
 #include "internal.hpp"
@@ -9,11 +10,6 @@ namespace gpiod {
 
 namespace {
 
-GPIOD_CXX_NORETURN void throw_bad_value_type()
-{
-	throw ::std::invalid_argument("bad value type for property");
-}
-
 request_config_ptr make_request_config()
 {
 	request_config_ptr config(::gpiod_request_config_new());
@@ -23,31 +19,6 @@ request_config_ptr make_request_config()
 	return config;
 }
 
-::std::string get_string_from_value(const ::std::any& val)
-{
-	if (val.type() == typeid(::std::string))
-		return ::std::any_cast<::std::string>(val);
-	else if (val.type() == typeid(const char*))
-		return ::std::any_cast<const char*>(val);
-
-	throw_bad_value_type();
-}
-
-unsigned int get_unsigned_int_from_value(const ::std::any& val)
-{
-	if (val.type() == typeid(unsigned int)) {
-		return ::std::any_cast<unsigned int>(val);
-	} else if (val.type() == typeid(int)) {
-		int bufsize = ::std::any_cast<int>(val);
-		if (bufsize < 0)
-			bufsize = 0;
-
-		return static_cast<unsigned int>(bufsize);
-	}
-
-	throw_bad_value_type();
-}
-
 } /* namespace */
 
 request_config::impl::impl()
@@ -56,11 +27,10 @@ request_config::impl::impl()
 
 }
 
-GPIOD_CXX_API request_config::request_config(const properties& props)
+GPIOD_CXX_API request_config::request_config()
 	: _m_priv(new impl)
 {
-	for (const auto& prop: props)
-		this->set_property(prop.first, prop.second);
+
 }
 
 GPIOD_CXX_API request_config::request_config(request_config&& other) noexcept
@@ -74,54 +44,26 @@ GPIOD_CXX_API request_config::~request_config()
 
 }
 
-GPIOD_CXX_API request_config& request_config::operator=(request_config&& other) noexcept
+request_config& request_config::operator=(const request_config& other)
 {
-	this->_m_priv = ::std::move(other._m_priv);
+	this->_m_priv = other._m_priv;
 
 	return *this;
 }
 
-GPIOD_CXX_API void request_config::set_property(property prop, const ::std::any& val)
-{
-	switch (prop) {
-	case property::OFFSETS:
-		try {
-			this->set_offsets(::std::any_cast<line::offsets>(val));
-		} catch (const ::std::bad_any_cast& ex) {
-			throw_bad_value_type();
-		}
-		break;
-	case property::CONSUMER:
-		this->set_consumer(get_string_from_value(val));
-		break;
-	case property::EVENT_BUFFER_SIZE:
-		this->set_event_buffer_size(get_unsigned_int_from_value(val));
-		break;
-	default:
-		throw ::std::invalid_argument("unknown property");
-	}
-}
-
-GPIOD_CXX_API void request_config::set_offsets(const line::offsets& offsets) noexcept
+GPIOD_CXX_API request_config& request_config::operator=(request_config&& other) noexcept
 {
-	::std::vector<unsigned int> buf(offsets.size());
-
-	for (unsigned int i = 0; i < offsets.size(); i++)
-		buf[i] = offsets[i];
-
-	::gpiod_request_config_set_offsets(this->_m_priv->config.get(),
-					   buf.size(), buf.data());
-}
+	this->_m_priv = ::std::move(other._m_priv);
 
-GPIOD_CXX_API ::std::size_t request_config::num_offsets() const noexcept
-{
-	return ::gpiod_request_config_get_num_offsets(this->_m_priv->config.get());
+	return *this;
 }
 
-GPIOD_CXX_API void
+GPIOD_CXX_API request_config&
 request_config::set_consumer(const ::std::string& consumer) noexcept
 {
 	::gpiod_request_config_set_consumer(this->_m_priv->config.get(), consumer.c_str());
+
+	return *this;
 }
 
 GPIOD_CXX_API ::std::string request_config::consumer() const noexcept
@@ -131,24 +73,13 @@ GPIOD_CXX_API ::std::string request_config::consumer() const noexcept
 	return consumer ?: "";
 }
 
-GPIOD_CXX_API line::offsets request_config::offsets() const
-{
-	line::offsets ret(this->num_offsets());
-	::std::vector<unsigned int> buf(this->num_offsets());
-
-	::gpiod_request_config_get_offsets(this->_m_priv->config.get(), buf.data());
-
-	for (unsigned int i = 0; i < this->num_offsets(); i++)
-		ret[i] = buf[i];
-
-	return ret;
-}
-
-GPIOD_CXX_API void
+GPIOD_CXX_API request_config&
 request_config::set_event_buffer_size(::std::size_t event_buffer_size) noexcept
 {
 	::gpiod_request_config_set_event_buffer_size(this->_m_priv->config.get(),
 						     event_buffer_size);
+
+	return *this;
 }
 
 GPIOD_CXX_API ::std::size_t request_config::event_buffer_size() const noexcept
@@ -163,8 +94,6 @@ GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const request_conf
 	consumer = config.consumer().empty() ? "N/A" : ::std::string("'") + config.consumer() + "'";
 
 	out << "gpiod::request_config(consumer=" << consumer <<
-	       ", num_offsets=" << config.num_offsets() <<
-	       ", offsets=(" << config.offsets() << ")" <<
 	       ", event_buffer_size=" << config.event_buffer_size() <<
 	       ")";
 
diff --git a/bindings/cxx/tests/Makefile.am b/bindings/cxx/tests/Makefile.am
index d40c069..4971dd4 100644
--- a/bindings/cxx/tests/Makefile.am
+++ b/bindings/cxx/tests/Makefile.am
@@ -22,10 +22,11 @@ gpiod_cxx_test_SOURCES =			\
 		tests-chip.cpp			\
 		tests-chip-info.cpp		\
 		tests-edge-event.cpp		\
+		tests-info-event.cpp		\
 		tests-line.cpp			\
 		tests-line-config.cpp		\
 		tests-line-info.cpp		\
 		tests-line-request.cpp		\
-		tests-info-event.cpp		\
+		tests-line-settings.cpp		\
 		tests-misc.cpp			\
-		tests-request-config.cpp
+		tests-request-config.cpp
\ No newline at end of file
diff --git a/bindings/cxx/tests/tests-edge-event.cpp b/bindings/cxx/tests/tests-edge-event.cpp
index 2cf9252..04eb57b 100644
--- a/bindings/cxx/tests/tests-edge-event.cpp
+++ b/bindings/cxx/tests/tests-edge-event.cpp
@@ -12,8 +12,6 @@
 #include "helpers.hpp"
 
 using simprop = ::gpiosim::chip::property;
-using reqprop = ::gpiod::request_config::property;
-using lineprop = ::gpiod::line_config::property;
 using direction = ::gpiod::line::direction;
 using edge = ::gpiod::line::edge;
 using offsets = ::gpiod::line::offsets;
@@ -45,14 +43,13 @@ TEST_CASE("edge_event wait timeout", "[edge-event]")
 	::gpiosim::chip sim;
 	::gpiod::chip chip(sim.dev_path());
 
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offsets({ 0 })}
-		}),
-		::gpiod::line_config({
-			{ lineprop::EDGE_DETECTION, edge::BOTH }
-		})
-	);
+	auto request = chip.prepare_request()
+		.add_line_settings(
+			0,
+			::gpiod::line_settings()
+				.set_edge_detection(edge::BOTH)
+		)
+		.do_request();
 
 	REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
 }
@@ -60,18 +57,17 @@ TEST_CASE("edge_event wait timeout", "[edge-event]")
 TEST_CASE("output mode and edge detection don't work together", "[edge-event]")
 {
 	::gpiosim::chip sim;
-	::gpiod::chip chip(sim.dev_path());
 
 	REQUIRE_THROWS_AS(
-		chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 0 })}
-			}),
-			::gpiod::line_config({
-				{ lineprop::DIRECTION, direction::OUTPUT },
-				{ lineprop::EDGE_DETECTION, edge::BOTH }
-			})
-		),
+		::gpiod::chip(sim.dev_path())
+			.prepare_request()
+			.add_line_settings(
+				0,
+				::gpiod::line_settings()
+					.set_direction(direction::OUTPUT)
+					.set_edge_detection(edge::BOTH)
+			)
+			.do_request(),
 		::std::invalid_argument
 	);
 }
@@ -101,14 +97,14 @@ TEST_CASE("waiting for and reading edge events works", "[edge-event]")
 
 	SECTION("both edge events")
 	{
-		auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 2 })}
-			}),
-			::gpiod::line_config({
-				{ lineprop::EDGE_DETECTION, edge::BOTH }
-			})
-		);
+		auto request = chip
+			.prepare_request()
+			.add_line_settings(
+				2,
+				::gpiod::line_settings()
+					.set_edge_detection(edge::BOTH)
+			)
+			.do_request();
 
 		::std::uint64_t ts_rising, ts_falling;
 
@@ -139,14 +135,14 @@ TEST_CASE("waiting for and reading edge events works", "[edge-event]")
 
 	SECTION("rising edge event")
 	{
-		auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 6 })}
-			}),
-			::gpiod::line_config({
-				{ lineprop::EDGE_DETECTION, edge::RISING }
-			})
-		);
+		auto request = chip
+			.prepare_request()
+			.add_line_settings(
+				6,
+				::gpiod::line_settings()
+					.set_edge_detection(edge::RISING)
+			)
+			.do_request();
 
 		::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 6);
 
@@ -164,14 +160,14 @@ TEST_CASE("waiting for and reading edge events works", "[edge-event]")
 
 	SECTION("falling edge event")
 	{
-		auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 7 })}
-			}),
-			::gpiod::line_config({
-				{ lineprop::EDGE_DETECTION, edge::FALLING }
-			})
-		);
+		auto request = chip
+			.prepare_request()
+			.add_line_settings(
+				7,
+				::gpiod::line_settings()
+					.set_edge_detection(edge::FALLING)
+			)
+			.do_request();
 
 		::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 7);
 
@@ -189,14 +185,14 @@ TEST_CASE("waiting for and reading edge events works", "[edge-event]")
 
 	SECTION("sequence numbers")
 	{
-		auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 0, 1 })}
-			}),
-			::gpiod::line_config({
-				{ lineprop::EDGE_DETECTION, edge::BOTH }
-			})
-		);
+		auto request = chip
+			.prepare_request()
+			.add_line_settings(
+				{ 0, 1 },
+				::gpiod::line_settings()
+					.set_edge_detection(edge::BOTH)
+			)
+			.do_request();
 
 		::std::thread thread(trigger_rising_edge_events_on_two_offsets, ::std::ref(sim), 0, 1);
 
@@ -227,14 +223,14 @@ TEST_CASE("reading multiple events", "[edge-event]")
 	::gpiosim::chip sim({{ simprop::NUM_LINES, 8 }});
 	::gpiod::chip chip(sim.dev_path());
 
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offsets({ 1 })}
-		}),
-		::gpiod::line_config({
-			{ lineprop::EDGE_DETECTION, edge::BOTH }
-		})
-	);
+	auto request = chip
+		.prepare_request()
+		.add_line_settings(
+			1,
+			::gpiod::line_settings()
+				.set_edge_detection(edge::BOTH)
+		)
+		.do_request();
 
 	unsigned long line_seqno = 1, global_seqno = 1;
 
@@ -277,14 +273,14 @@ TEST_CASE("edge_event_buffer can be moved", "[edge-event]")
 	::gpiod::edge_event_buffer buffer(13);
 
 	/* Get some events into the buffer. */
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offsets({ 1 })}
-		}),
-		::gpiod::line_config({
-			{ lineprop::EDGE_DETECTION, edge::BOTH }
-		})
-	);
+	auto request = chip
+		.prepare_request()
+		.add_line_settings(
+			1,
+			::gpiod::line_settings()
+				.set_edge_detection(edge::BOTH)
+		)
+		.do_request();
 
 	sim.set_pull(1, pull::PULL_UP);
 	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
@@ -321,14 +317,14 @@ TEST_CASE("edge_event can be copied and moved", "[edge-event]")
 	::gpiod::chip chip(sim.dev_path());
 	::gpiod::edge_event_buffer buffer;
 
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offsets({ 0 })}
-		}),
-		::gpiod::line_config({
-			{ lineprop::EDGE_DETECTION, edge::BOTH }
-		})
-	);
+	auto request = chip
+		.prepare_request()
+		.add_line_settings(
+			0,
+			::gpiod::line_settings()
+				.set_edge_detection(edge::BOTH)
+		)
+		.do_request();
 
 	sim.set_pull(0, pull::PULL_UP);
 	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
@@ -387,14 +383,14 @@ TEST_CASE("stream insertion operators work for edge_event and edge_event_buffer"
 	::gpiod::edge_event_buffer buffer;
 	::std::stringstream sbuf, expected;
 
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offsets({ 0 })}
-		}),
-		::gpiod::line_config({
-			{ lineprop::EDGE_DETECTION, edge::BOTH }
-		})
-	);
+	auto request = chip
+		.prepare_request()
+		.add_line_settings(
+			0,
+			::gpiod::line_settings()
+				.set_edge_detection(edge::BOTH)
+		)
+		.do_request();
 
 	sim.set_pull(0, pull::PULL_UP);
 	::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
diff --git a/bindings/cxx/tests/tests-info-event.cpp b/bindings/cxx/tests/tests-info-event.cpp
index b838d5c..788da8f 100644
--- a/bindings/cxx/tests/tests-info-event.cpp
+++ b/bindings/cxx/tests/tests-info-event.cpp
@@ -12,8 +12,6 @@
 #include "helpers.hpp"
 
 using simprop = ::gpiosim::chip::property;
-using reqprop = ::gpiod::request_config::property;
-using lineprop = ::gpiod::line_config::property;
 using direction = ::gpiod::line::direction;
 using event_type = ::gpiod::info_event::event_type;
 
@@ -23,19 +21,20 @@ void request_reconfigure_release_line(::gpiod::chip& chip)
 {
 	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
 
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, ::gpiod::line::offsets({ 7 }) }
-		}),
-		::gpiod::line_config()
-	);
+	auto request = chip
+		.prepare_request()
+		.add_line_settings(7, ::gpiod::line_settings())
+		.do_request();
 
 	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
 
 	request.reconfigure_lines(
-		::gpiod::line_config({
-			{ lineprop::DIRECTION, direction::OUTPUT }
-		})
+		::gpiod::line_config()
+			.add_line_settings(
+				7,
+				::gpiod::line_settings()
+					.set_direction(direction::OUTPUT)
+			)
 	);
 
 	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
@@ -109,12 +108,10 @@ TEST_CASE("info_event can be copied and moved", "[info-event]")
 
 	chip.watch_line_info(0);
 
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, ::gpiod::line::offsets({ 0 }) }
-		}),
-		::gpiod::line_config()
-	);
+	auto request = chip
+		.prepare_request()
+		.add_line_settings(0, ::gpiod::line_settings())
+		.do_request();
 
 	REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
 	auto event = chip.read_info_event();
@@ -176,12 +173,10 @@ TEST_CASE("info_event stream insertion operator works", "[info-event][line-info]
 
 	chip.watch_line_info(0);
 
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, ::gpiod::line::offsets({ 0 }) }
-		}),
-		::gpiod::line_config()
-	);
+	auto request = chip
+		.prepare_request()
+		.add_line_settings(0, ::gpiod::line_settings())
+		.do_request();
 
 	auto event = chip.read_info_event();
 
diff --git a/bindings/cxx/tests/tests-line-config.cpp b/bindings/cxx/tests/tests-line-config.cpp
index e1dcd6e..5fa0f94 100644
--- a/bindings/cxx/tests/tests-line-config.cpp
+++ b/bindings/cxx/tests/tests-line-config.cpp
@@ -3,21 +3,13 @@
 
 #include <catch2/catch.hpp>
 #include <gpiod.hpp>
-#include <sstream>
 
 #include "helpers.hpp"
 
-using lineprop = ::gpiod::line_config::property;
-using value = ::gpiod::line::value;
+using namespace ::std::chrono_literals;
 using direction = ::gpiod::line::direction;
-using edge = ::gpiod::line::edge;
-using bias = ::gpiod::line::bias;
 using drive = ::gpiod::line::drive;
-using clock_type = ::gpiod::line::clock;
-using mappings = ::gpiod::line::value_mappings;
-using offsets = ::gpiod::line::offsets;
-
-using namespace ::std::chrono_literals;
+using edge = ::gpiod::line::edge;
 
 namespace {
 
@@ -27,244 +19,86 @@ TEST_CASE("line_config constructor works", "[line-config]")
 	{
 		::gpiod::line_config cfg;
 
-		REQUIRE_NOTHROW(cfg.direction_default() == direction::INPUT);
-		REQUIRE(cfg.edge_detection_default() == edge::NONE);
-		REQUIRE(cfg.bias_default() == bias::AS_IS);
-		REQUIRE(cfg.drive_default() == drive::PUSH_PULL);
-		REQUIRE_FALSE(cfg.active_low_default());
-		REQUIRE(cfg.debounce_period_default() == 0us);
-		REQUIRE(cfg.event_clock_default() == clock_type::MONOTONIC);
-		REQUIRE(cfg.output_value_default() == value::INACTIVE);
-		REQUIRE(cfg.num_overrides() == 0);
-		REQUIRE(cfg.overrides().empty());
-	}
-
-	SECTION("default values set from constructor")
-	{
-		/*
-		 * These are wrong and the request would fail but we're just
-		 * testing the object's behavior.
-		 */
-		::gpiod::line_config cfg({
-			{ lineprop::DIRECTION, direction::OUTPUT },
-			{ lineprop::EDGE_DETECTION, edge::FALLING },
-			{ lineprop::BIAS, bias::DISABLED },
-			{ lineprop::DRIVE, drive::OPEN_DRAIN },
-			{ lineprop::ACTIVE_LOW, true },
-			{ lineprop::DEBOUNCE_PERIOD, 3000us },
-			{ lineprop::EVENT_CLOCK, clock_type::REALTIME },
-			{ lineprop::OUTPUT_VALUE, value::ACTIVE }
-		});
-
-		REQUIRE_NOTHROW(cfg.direction_default() == direction::OUTPUT);
-		REQUIRE(cfg.edge_detection_default() == edge::FALLING);
-		REQUIRE(cfg.bias_default() == bias::DISABLED);
-		REQUIRE(cfg.drive_default() == drive::OPEN_DRAIN);
-		REQUIRE(cfg.active_low_default());
-		/* Test implicit conversion between duration types. */
-		REQUIRE(cfg.debounce_period_default() == 3ms);
-		REQUIRE(cfg.event_clock_default() == clock_type::REALTIME);
-		REQUIRE(cfg.output_value_default() == value::ACTIVE);
-		REQUIRE(cfg.num_overrides() == 0);
-		REQUIRE(cfg.overrides().empty());
-	}
-
-	SECTION("output value overrides can be set from constructor")
-	{
-		::gpiod::line_config cfg({
-			{
-				lineprop::OUTPUT_VALUES, mappings({
-					{ 0, value::ACTIVE },
-					{ 3, value::INACTIVE },
-					{ 1, value::ACTIVE }
-				})
-			}
-		});
-
-		REQUIRE(cfg.num_overrides() == 3);
-		auto overrides = cfg.overrides();
-		REQUIRE(overrides[0].first == 0);
-		REQUIRE(overrides[0].second == lineprop::OUTPUT_VALUE);
-		REQUIRE(overrides[1].first == 3);
-		REQUIRE(overrides[1].second == lineprop::OUTPUT_VALUE);
-		REQUIRE(overrides[2].first == 1);
-		REQUIRE(overrides[2].second == lineprop::OUTPUT_VALUE);
+		REQUIRE(cfg.get_line_settings().size() == 0);
 	}
 }
 
-TEST_CASE("line_config overrides work")
+TEST_CASE("adding line_settings to line_config works", "[line-config][line-settings]")
 {
 	::gpiod::line_config cfg;
 
-	SECTION("direction")
-	{
-		cfg.set_direction_default(direction::AS_IS);
-		cfg.set_direction_override(direction::INPUT, 3);
-
-		REQUIRE(cfg.direction_is_overridden(3));
-		REQUIRE(cfg.direction_offset(3) == direction::INPUT);
-		cfg.clear_direction_override(3);
-		REQUIRE_FALSE(cfg.direction_is_overridden(3));
-		REQUIRE(cfg.direction_offset(3) == direction::AS_IS);
-	}
-
-	SECTION("edge detection")
-	{
-		cfg.set_edge_detection_default(edge::NONE);
-		cfg.set_edge_detection_override(edge::BOTH, 0);
-
-		REQUIRE(cfg.edge_detection_is_overridden(0));
-		REQUIRE(cfg.edge_detection_offset(0) == edge::BOTH);
-		cfg.clear_edge_detection_override(0);
-		REQUIRE_FALSE(cfg.edge_detection_is_overridden(0));
-		REQUIRE(cfg.edge_detection_offset(0) == edge::NONE);
-	}
-
-	SECTION("bias")
-	{
-		cfg.set_bias_default(bias::AS_IS);
-		cfg.set_bias_override(bias::PULL_DOWN, 3);
-
-		REQUIRE(cfg.bias_is_overridden(3));
-		REQUIRE(cfg.bias_offset(3) == bias::PULL_DOWN);
-		cfg.clear_bias_override(3);
-		REQUIRE_FALSE(cfg.bias_is_overridden(3));
-		REQUIRE(cfg.bias_offset(3) == bias::AS_IS);
-	}
-
-	SECTION("drive")
-	{
-		cfg.set_drive_default(drive::PUSH_PULL);
-		cfg.set_drive_override(drive::OPEN_DRAIN, 4);
-
-		REQUIRE(cfg.drive_is_overridden(4));
-		REQUIRE(cfg.drive_offset(4) == drive::OPEN_DRAIN);
-		cfg.clear_drive_override(4);
-		REQUIRE_FALSE(cfg.drive_is_overridden(4));
-		REQUIRE(cfg.drive_offset(4) == drive::PUSH_PULL);
-	}
-
-	SECTION("active-low")
-	{
-		cfg.set_active_low_default(false);
-		cfg.set_active_low_override(true, 16);
-
-		REQUIRE(cfg.active_low_is_overridden(16));
-		REQUIRE(cfg.active_low_offset(16));
-		cfg.clear_active_low_override(16);
-		REQUIRE_FALSE(cfg.active_low_is_overridden(16));
-		REQUIRE_FALSE(cfg.active_low_offset(16));
-	}
-
-	SECTION("debounce period")
-	{
-		/*
-		 * Test the chrono literals and implicit duration conversions
-		 * too.
-		 */
+	cfg.add_line_settings(4,
+		::gpiod::line_settings()
+			.set_direction(direction::INPUT)
+			.set_edge_detection(edge::RISING));
+
+	cfg.add_line_settings({7, 2},
+		::gpiod::line_settings()
+			.set_direction(direction::OUTPUT)
+			.set_drive(drive::OPEN_DRAIN));
+
+	auto settings = cfg.get_line_settings();
+
+	REQUIRE(settings.size() == 3);
+	REQUIRE(settings.at(2).direction() == direction::OUTPUT);
+	REQUIRE(settings.at(2).drive() == drive::OPEN_DRAIN);
+	REQUIRE(settings.at(4).direction() == direction::INPUT);
+	REQUIRE(settings.at(4).edge_detection() == edge::RISING);
+	REQUIRE(settings.at(7).direction() == direction::OUTPUT);
+	REQUIRE(settings.at(7).drive() == drive::OPEN_DRAIN);
+}
 
-		cfg.set_debounce_period_default(5000us);
-		cfg.set_debounce_period_override(3ms, 1);
+TEST_CASE("line_config can be reset", "[line-config]")
+{
+	::gpiod::line_config cfg;
 
-		REQUIRE(cfg.debounce_period_is_overridden(1));
-		REQUIRE(cfg.debounce_period_offset(1) == 3ms);
-		cfg.clear_debounce_period_override(1);
-		REQUIRE_FALSE(cfg.debounce_period_is_overridden(1));
-		REQUIRE(cfg.debounce_period_offset(1) == 5ms);
-	}
+	cfg.add_line_settings({3, 4, 7},
+		::gpiod::line_settings()
+			.set_direction(direction::INPUT)
+			.set_edge_detection(edge::BOTH));
 
-	SECTION("event clock")
-	{
-		cfg.set_event_clock_default(clock_type::MONOTONIC);
-		cfg.set_event_clock_override(clock_type::REALTIME, 4);
+	auto settings = cfg.get_line_settings();
 
-		REQUIRE(cfg.event_clock_is_overridden(4));
-		REQUIRE(cfg.event_clock_offset(4) == clock_type::REALTIME);
-		cfg.clear_event_clock_override(4);
-		REQUIRE_FALSE(cfg.event_clock_is_overridden(4));
-		REQUIRE(cfg.event_clock_offset(4) == clock_type::MONOTONIC);
-	}
+	REQUIRE(settings.size() == 3);
+	REQUIRE(settings.at(3).direction() == direction::INPUT);
+	REQUIRE(settings.at(3).edge_detection() == edge::BOTH);
+	REQUIRE(settings.at(4).direction() == direction::INPUT);
+	REQUIRE(settings.at(4).edge_detection() == edge::BOTH);
+	REQUIRE(settings.at(7).direction() == direction::INPUT);
+	REQUIRE(settings.at(7).edge_detection() == edge::BOTH);
 
-	SECTION("output value")
-	{
-		cfg.set_output_value_default(value::INACTIVE);
-		cfg.set_output_value_override(value::ACTIVE, 0);
-		cfg.set_output_values({ 1, 2, 8 }, { value::ACTIVE, value::ACTIVE, value::ACTIVE });
-		cfg.set_output_values({ { 17, value::ACTIVE }, { 21, value::ACTIVE } });
+	cfg.reset();
 
-		for (const auto& off: offsets({ 0, 1, 2, 8, 17, 21 })) {
-			REQUIRE(cfg.output_value_is_overridden(off));
-			REQUIRE(cfg.output_value_offset(off) == value::ACTIVE);
-			cfg.clear_output_value_override(off);
-			REQUIRE_FALSE(cfg.output_value_is_overridden(off));
-			REQUIRE(cfg.output_value_offset(off) == value::INACTIVE);
-		}
-	}
+	REQUIRE(cfg.get_line_settings().size() == 0);
 }
 
-TEST_CASE("line_config can be moved", "[line-config]")
+TEST_CASE("line_config stream insertion operator works", "[line-config]")
 {
-	::gpiod::line_config cfg({
-		{ lineprop::DIRECTION, direction::INPUT },
-		{ lineprop::EDGE_DETECTION, edge::BOTH },
-		{ lineprop::DEBOUNCE_PERIOD, 3000us },
-		{ lineprop::EVENT_CLOCK, clock_type::REALTIME },
-	});
-
-	cfg.set_direction_override(direction::OUTPUT, 2);
-	cfg.set_edge_detection_override(edge::NONE, 2);
+	::gpiod::line_config cfg;
 
-	SECTION("move constructor works")
+	SECTION("empty config")
 	{
-		auto moved(::std::move(cfg));
-
-		REQUIRE(moved.direction_default() == direction::INPUT);
-		REQUIRE(moved.edge_detection_default() == edge::BOTH);
-		REQUIRE(moved.debounce_period_default() == 3000us);
-		REQUIRE(moved.event_clock_default() == clock_type::REALTIME);
-		REQUIRE(moved.direction_offset(2) == direction::OUTPUT);
-		REQUIRE(moved.edge_detection_offset(2) == edge::NONE);
+		REQUIRE_THAT(cfg, stringify_matcher<::gpiod::line_config>(
+					"gpiod::line_config(num_settings=0)"));
 	}
 
-	SECTION("move constructor works")
+	SECTION("config with settings")
 	{
-		::gpiod::line_config moved;
-
-		moved = ::std::move(cfg);
-
-		REQUIRE(moved.direction_default() == direction::INPUT);
-		REQUIRE(moved.edge_detection_default() == edge::BOTH);
-		REQUIRE(moved.debounce_period_default() == 3000us);
-		REQUIRE(moved.event_clock_default() == clock_type::REALTIME);
-		REQUIRE(moved.direction_offset(2) == direction::OUTPUT);
-		REQUIRE(moved.edge_detection_offset(2) == edge::NONE);
+		cfg.add_line_settings({0, 2},
+				::gpiod::line_settings()
+					.set_direction(direction::OUTPUT)
+					.set_drive(drive::OPEN_SOURCE)
+		);
+
+		REQUIRE_THAT(cfg, stringify_matcher<::gpiod::line_config>(
+			"gpiod::line_config(num_settings=2, "
+			"settings=[0: gpiod::line_settings(direction=OUTPUT, edge_detection=NONE, "
+			"bias=AS_IS, drive=OPEN_SOURCE, active-high, debounce_period=0, "
+			"event_clock=MONOTONIC, output_value=INACTIVE), "
+			"2: gpiod::line_settings(direction=OUTPUT, edge_detection=NONE, bias=AS_IS, "
+			"drive=OPEN_SOURCE, active-high, debounce_period=0, event_clock=MONOTONIC, "
+			"output_value=INACTIVE)])"));
 	}
 }
 
-TEST_CASE("line_config stream insertion operator works", "[line-config]")
-{
-	::gpiod::line_config cfg({
-		{ lineprop::DIRECTION, direction::INPUT },
-		{ lineprop::EDGE_DETECTION, edge::BOTH },
-		{ lineprop::DEBOUNCE_PERIOD, 3000us },
-		{ lineprop::EVENT_CLOCK, clock_type::REALTIME },
-	});
-
-	cfg.set_direction_override(direction::OUTPUT, 2);
-	cfg.set_edge_detection_override(edge::NONE, 2);
-
-	::std::stringstream buf;
-
-	buf << cfg;
-
-	::std::string expected(
-		"gpiod::line_config(defaults=(direction=INPUT, edge_detection=BOTH_EDGES, bias="
-		"AS_IS, drive=PUSH_PULL, active-high, debounce_period=3000us, event_clock="
-		"REALTIME, default_output_value=INACTIVE), overrides=[(offset=2 -> direction="
-		"OUTPUT), (offset=2 -> edge_detection=NONE)])"
-	);
-
-	REQUIRE_THAT(buf.str(), Catch::Equals(expected));
-}
-
 } /* namespace */
diff --git a/bindings/cxx/tests/tests-line-request.cpp b/bindings/cxx/tests/tests-line-request.cpp
index 59692a7..3e7bcce 100644
--- a/bindings/cxx/tests/tests-line-request.cpp
+++ b/bindings/cxx/tests/tests-line-request.cpp
@@ -11,8 +11,6 @@
 #include "helpers.hpp"
 
 using simprop = ::gpiosim::chip::property;
-using reqprop = ::gpiod::request_config::property;
-using lineprop = ::gpiod::line_config::property;
 using offsets = ::gpiod::line::offsets;
 using values = ::gpiod::line::values;
 using direction = ::gpiod::line::direction;
@@ -60,37 +58,37 @@ private:
 	bool _m_active_low;
 };
 
-TEST_CASE("requesting lines fails with invalid arguments", "[line-request][chip]")
+TEST_CASE("requesting lines behaves correctly with invalid arguments", "[line-request][chip]")
 {
 	::gpiosim::chip sim({{ simprop::NUM_LINES, 8 }});
 	::gpiod::chip chip(sim.dev_path());
 
 	SECTION("no offsets")
 	{
-		REQUIRE_THROWS_AS(chip.request_lines(::gpiod::request_config(),
-						     ::gpiod::line_config()),
-				  ::std::invalid_argument);
+		REQUIRE_THROWS_AS(chip.prepare_request().do_request(), ::std::invalid_argument);
 	}
 
 	SECTION("duplicate offsets")
 	{
-		REQUIRE_THROWS_MATCHES(chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 2, 0, 0, 4 }) }
-			}),
-			::gpiod::line_config()),
-			::std::system_error,
-			 system_error_matcher(EBUSY)
-		);
+		auto request = chip
+			.prepare_request()
+			.add_line_settings({ 2, 0, 0, 4 }, ::gpiod::line_settings())
+			.do_request();
+
+		auto offsets = request.offsets();
+
+		REQUIRE(offsets.size() == 3);
+		REQUIRE(offsets[0] == 2);
+		REQUIRE(offsets[1] == 0);
+		REQUIRE(offsets[2] == 4);
 	}
 
 	SECTION("offset out of bounds")
 	{
-		REQUIRE_THROWS_AS(chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 2, 0, 8, 4 }) }
-			}),
-			::gpiod::line_config()),
+		REQUIRE_THROWS_AS(chip
+			.prepare_request()
+			.add_line_settings({ 2, 0, 8, 4 }, ::gpiod::line_settings())
+			.do_request(),
 			::std::invalid_argument
 		);
 	}
@@ -104,13 +102,11 @@ TEST_CASE("consumer string is set correctly", "[line-request]")
 
 	SECTION("set custom consumer")
 	{
-		auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 2 }) },
-				{ reqprop::CONSUMER, "foobar" }
-			}),
-			::gpiod::line_config()
-		);
+		auto request = chip
+			.prepare_request()
+			.add_line_settings(offs, ::gpiod::line_settings())
+			.set_consumer("foobar")
+			.do_request();
 
 		auto info = chip.get_line_info(2);
 
@@ -120,12 +116,10 @@ TEST_CASE("consumer string is set correctly", "[line-request]")
 
 	SECTION("empty consumer")
 	{
-		auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 2 }) },
-			}),
-			::gpiod::line_config()
-		);
+		auto request = chip
+			.prepare_request()
+			.add_line_settings(2, ::gpiod::line_settings())
+			.do_request();
 
 		auto info = chip.get_line_info(2);
 
@@ -150,14 +144,14 @@ TEST_CASE("values can be read", "[line-request]")
 	for (unsigned int i = 0; i < offs.size(); i++)
 		sim.set_pull(offs[i], pulls[i]);
 
-	auto request = ::gpiod::chip(sim.dev_path()).request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offs }
-		}),
-		::gpiod::line_config({
-			{ lineprop::DIRECTION, direction::INPUT }
-		})
-	);
+	auto request = ::gpiod::chip(sim.dev_path())
+		.prepare_request()
+		.add_line_settings(
+			offs,
+			::gpiod::line_settings()
+				.set_direction(direction::INPUT)
+		)
+		.do_request();
 
 	SECTION("get all values (returning variant)")
 	{
@@ -201,9 +195,11 @@ TEST_CASE("values can be read", "[line-request]")
 	SECTION("get a single value (active-low)")
 	{
 		request.reconfigure_lines(
-			::gpiod::line_config({
-				{ lineprop::ACTIVE_LOW, true }
-			})
+			::gpiod::line_config()
+				.add_line_settings(
+					offs,
+					::gpiod::line_settings()
+						.set_active_low(true))
 		);
 
 		auto val = request.get_value(7);
@@ -238,18 +234,20 @@ TEST_CASE("output values can be set at request time", "[line-request]")
 	::gpiod::chip chip(sim.dev_path());
 	const offsets offs({ 0, 1, 3, 4 });
 
-	::gpiod::request_config req_cfg({
-		{ reqprop::OFFSETS, offs }
-	});
+	::gpiod::line_settings settings;
+	settings
+		.set_direction(direction::OUTPUT)
+		.set_output_value(value::ACTIVE);
 
-	::gpiod::line_config line_cfg({
-		{ lineprop::DIRECTION, direction::OUTPUT },
-		{ lineprop::OUTPUT_VALUE, value::ACTIVE }
-	});
+	::gpiod::line_config line_cfg;
+	line_cfg.add_line_settings(offs, settings);
 
 	SECTION("default output value")
 	{
-		auto request = chip.request_lines(req_cfg, line_cfg);
+		auto request = chip
+			.prepare_request()
+			.set_line_config(line_cfg)
+			.do_request();
 
 		for (const auto& off: offs)
 			REQUIRE(sim.get_value(off) == simval::ACTIVE);
@@ -259,9 +257,13 @@ TEST_CASE("output values can be set at request time", "[line-request]")
 
 	SECTION("overridden output value")
 	{
-		line_cfg.set_output_value_override(value::INACTIVE, 1);
+		settings.set_output_value(value::INACTIVE);
+		line_cfg.add_line_settings(1, settings);
 
-		auto request = chip.request_lines(req_cfg, line_cfg);
+		auto request = chip
+			.prepare_request()
+			.set_line_config(line_cfg)
+			.do_request();
 
 		REQUIRE(sim.get_value(0) == simval::ACTIVE);
 		REQUIRE(sim.get_value(1) == simval::INACTIVE);
@@ -276,16 +278,14 @@ TEST_CASE("values can be set after requesting lines", "[line-request]")
 	::gpiosim::chip sim({{ simprop::NUM_LINES, 8 }});
 	const offsets offs({ 0, 1, 3, 4 });
 
-	::gpiod::request_config req_cfg({
-		{ reqprop::OFFSETS, offs }
-	});
-
-	::gpiod::line_config line_cfg({
-		{ lineprop::DIRECTION, direction::OUTPUT },
-		{ lineprop::OUTPUT_VALUE, value::INACTIVE }
-	});
-
-	auto request = ::gpiod::chip(sim.dev_path()).request_lines(req_cfg, line_cfg);
+	auto request = ::gpiod::chip(sim.dev_path())
+		.prepare_request()
+		.add_line_settings(
+			offs,
+			::gpiod::line_settings()
+				.set_direction(direction::OUTPUT)
+		)
+		.do_request();
 
 	SECTION("set single value")
 	{
@@ -343,21 +343,20 @@ TEST_CASE("line_request can be moved", "[line-request]")
 	::gpiod::chip chip(sim.dev_path());
 	const offsets offs({ 3, 1, 0, 2 });
 
-	auto request = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offs }
-		}),
-		::gpiod::line_config()
-	);
+	auto request = chip
+		.prepare_request()
+		.add_line_settings(
+			offs,
+			::gpiod::line_settings()
+		)
+		.do_request();
 
 	auto fd = request.fd();
 
-	auto another = chip.request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offsets({ 6 }) }
-		}),
-		::gpiod::line_config()
-	);
+	auto another = chip
+		.prepare_request()
+		.add_line_settings(6, ::gpiod::line_settings())
+		.do_request();
 
 	SECTION("move constructor works")
 	{
@@ -380,12 +379,10 @@ TEST_CASE("released request can no longer be used", "[line-request]")
 {
 	::gpiosim::chip sim;
 
-	auto request = ::gpiod::chip(sim.dev_path()).request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offsets({ 0 }) }
-		}),
-		::gpiod::line_config()
-	);
+	auto request = ::gpiod::chip(sim.dev_path())
+		.prepare_request()
+		.add_line_settings(0, ::gpiod::line_settings())
+		.do_request();
 
 	request.release();
 
@@ -402,14 +399,14 @@ TEST_CASE("line_request survives parent chip", "[line-request][chip]")
 	{
 		::gpiod::chip chip(sim.dev_path());
 
-		auto request = chip.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 0 }) }
-			}),
-			::gpiod::line_config({
-				{ lineprop::DIRECTION, direction::INPUT }
-			})
-		);
+		auto request = chip
+			.prepare_request()
+			.add_line_settings(
+				0,
+				::gpiod::line_settings()
+					.set_direction(direction::INPUT)
+			)
+			.do_request();
 
 		REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
 
@@ -422,15 +419,13 @@ TEST_CASE("line_request survives parent chip", "[line-request][chip]")
 	{
 		/* Need to get the request object somehow. */
 		::gpiod::chip dummy(sim.dev_path());
+		::gpiod::line_config cfg;
+		cfg.add_line_settings(0, ::gpiod::line_settings().set_direction(direction::INPUT));
 
-		auto request = dummy.request_lines(
-			::gpiod::request_config({
-				{ reqprop::OFFSETS, offsets({ 0 }) }
-			}),
-			::gpiod::line_config({
-				{ lineprop::DIRECTION, direction::INPUT }
-			})
-		);
+		auto request = dummy
+			.prepare_request()
+			.set_line_config(cfg)
+			.do_request();
 
 		request.release();
 		dummy.close();
@@ -438,14 +433,10 @@ TEST_CASE("line_request survives parent chip", "[line-request][chip]")
 		{
 			::gpiod::chip chip(sim.dev_path());
 
-			request = chip.request_lines(
-				::gpiod::request_config({
-					{ reqprop::OFFSETS, offsets({ 0 }) }
-				}),
-				::gpiod::line_config({
-					{ lineprop::DIRECTION, direction::INPUT }
-				})
-			);
+			request = chip
+				.prepare_request()
+				.set_line_config(cfg)
+				.do_request();
 
 			REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
 		}
@@ -458,12 +449,10 @@ TEST_CASE("line_request stream insertion operator works", "[line-request]")
 {
 	::gpiosim::chip sim({{ simprop::NUM_LINES, 4 }});
 
-	auto request = ::gpiod::chip(sim.dev_path()).request_lines(
-		::gpiod::request_config({
-			{ reqprop::OFFSETS, offsets({ 3, 1, 0, 2 }) }
-		}),
-		::gpiod::line_config()
-	);
+	auto request = ::gpiod::chip(sim.dev_path())
+		.prepare_request()
+		.add_line_settings({ 3, 1, 0, 2}, ::gpiod::line_settings())
+		.do_request();
 
 	::std::stringstream buf, expected;
 
diff --git a/bindings/cxx/tests/tests-line-settings.cpp b/bindings/cxx/tests/tests-line-settings.cpp
new file mode 100644
index 0000000..a7801a4
--- /dev/null
+++ b/bindings/cxx/tests/tests-line-settings.cpp
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+
+#include "helpers.hpp"
+
+using value = ::gpiod::line::value;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using bias = ::gpiod::line::bias;
+using drive = ::gpiod::line::drive;
+using clock_type = ::gpiod::line::clock;
+using value = ::gpiod::line::value;
+
+using namespace ::std::chrono_literals;
+
+namespace {
+
+TEST_CASE("line_settings constructor works", "[line-settings]")
+{
+	::gpiod::line_settings settings;
+
+	REQUIRE(settings.direction() == direction::AS_IS);
+	REQUIRE(settings.edge_detection() == edge::NONE);
+	REQUIRE(settings.bias() == bias::AS_IS);
+	REQUIRE(settings.drive() == drive::PUSH_PULL);
+	REQUIRE_FALSE(settings.active_low());
+	REQUIRE(settings.debounce_period() == 0us);
+	REQUIRE(settings.event_clock() == clock_type::MONOTONIC);
+	REQUIRE(settings.output_value() == value::INACTIVE);
+}
+
+TEST_CASE("line_settings mutators work", "[line-settings]")
+{
+	::gpiod::line_settings settings;
+
+	SECTION("direction")
+	{
+		settings.set_direction(direction::INPUT);
+		REQUIRE(settings.direction() == direction::INPUT);
+		settings.set_direction(direction::AS_IS);
+		REQUIRE(settings.direction() == direction::AS_IS);
+		settings.set_direction(direction::OUTPUT);
+		REQUIRE(settings.direction() == direction::OUTPUT);
+		REQUIRE_THROWS_AS(settings.set_direction(static_cast<direction>(999)),
+				  ::std::invalid_argument);
+	}
+
+	SECTION("edge detection")
+	{
+		settings.set_edge_detection(edge::BOTH);
+		REQUIRE(settings.edge_detection() == edge::BOTH);
+		settings.set_edge_detection(edge::NONE);
+		REQUIRE(settings.edge_detection() == edge::NONE);
+		settings.set_edge_detection(edge::FALLING);
+		REQUIRE(settings.edge_detection() == edge::FALLING);
+		settings.set_edge_detection(edge::RISING);
+		REQUIRE(settings.edge_detection() == edge::RISING);
+		REQUIRE_THROWS_AS(settings.set_edge_detection(static_cast<edge>(999)),
+				  ::std::invalid_argument);
+	}
+
+	SECTION("bias")
+	{
+		settings.set_bias(bias::DISABLED);
+		REQUIRE(settings.bias() == bias::DISABLED);
+		settings.set_bias(bias::AS_IS);
+		REQUIRE(settings.bias() == bias::AS_IS);
+		settings.set_bias(bias::PULL_DOWN);
+		REQUIRE(settings.bias() == bias::PULL_DOWN);
+		settings.set_bias(bias::PULL_UP);
+		REQUIRE(settings.bias() == bias::PULL_UP);
+		REQUIRE_THROWS_AS(settings.set_bias(static_cast<bias>(999)), ::std::invalid_argument);
+		REQUIRE_THROWS_AS(settings.set_bias(bias::UNKNOWN), ::std::invalid_argument);
+	}
+
+	SECTION("drive")
+	{
+		settings.set_drive(drive::OPEN_DRAIN);
+		REQUIRE(settings.drive() == drive::OPEN_DRAIN);
+		settings.set_drive(drive::PUSH_PULL);
+		REQUIRE(settings.drive() == drive::PUSH_PULL);
+		settings.set_drive(drive::OPEN_SOURCE);
+		REQUIRE(settings.drive() == drive::OPEN_SOURCE);
+		REQUIRE_THROWS_AS(settings.set_drive(static_cast<drive>(999)), ::std::invalid_argument);
+	}
+
+	SECTION("active-low")
+	{
+		settings.set_active_low(true);
+		REQUIRE(settings.active_low());
+		settings.set_active_low(false);
+		REQUIRE_FALSE(settings.active_low());
+	}
+
+	SECTION("debounce period")
+	{
+		settings.set_debounce_period(2000us);
+		REQUIRE(settings.debounce_period() == 2000us);
+	}
+
+	SECTION("event clock")
+	{
+		settings.set_event_clock(clock_type::REALTIME);
+		REQUIRE(settings.event_clock() == clock_type::REALTIME);
+		settings.set_event_clock(clock_type::MONOTONIC);
+		REQUIRE(settings.event_clock() == clock_type::MONOTONIC);
+		REQUIRE_THROWS_AS(settings.set_event_clock(static_cast<clock_type>(999)),
+				  ::std::invalid_argument);
+	}
+
+	SECTION("output value")
+	{
+		settings.set_output_value(value::ACTIVE);
+		REQUIRE(settings.output_value() == value::ACTIVE);
+		settings.set_output_value(value::INACTIVE);
+		REQUIRE(settings.output_value() == value::INACTIVE);
+		REQUIRE_THROWS_AS(settings.set_output_value(static_cast<value>(999)),
+				  ::std::invalid_argument);
+	}
+}
+
+TEST_CASE("line_settings stream insertion operator works", "[line-settings]")
+{
+	::gpiod::line_settings settings;
+
+	REQUIRE_THAT(settings
+		.set_active_low(true)
+		.set_direction(direction::INPUT)
+		.set_edge_detection(edge::BOTH)
+		.set_bias(bias::PULL_DOWN)
+		.set_event_clock(clock_type::REALTIME),
+		stringify_matcher<::gpiod::line_settings>(
+			"gpiod::line_settings(direction=INPUT, edge_detection=BOTH_EDGES, "
+			"bias=PULL_DOWN, drive=PUSH_PULL, active-low, debounce_period=0, "
+			"event_clock=REALTIME, output_value=INACTIVE)"
+		)
+	);
+}
+
+} /* namespace */
diff --git a/bindings/cxx/tests/tests-request-config.cpp b/bindings/cxx/tests/tests-request-config.cpp
index ddec724..66eb748 100644
--- a/bindings/cxx/tests/tests-request-config.cpp
+++ b/bindings/cxx/tests/tests-request-config.cpp
@@ -9,7 +9,6 @@
 
 #include "helpers.hpp"
 
-using property = ::gpiod::request_config::property;
 using offsets = ::gpiod::line::offsets;
 
 namespace {
@@ -21,56 +20,20 @@ TEST_CASE("request_config constructor works", "[request-config]")
 		::gpiod::request_config cfg;
 
 		REQUIRE(cfg.consumer().empty());
-		REQUIRE(cfg.offsets().empty());
 		REQUIRE(cfg.event_buffer_size() == 0);
 	}
-
-	SECTION("constructor with default settings")
-	{
-		offsets offsets({ 0, 1, 2, 3 });
-
-		::gpiod::request_config cfg({
-			{ property::CONSUMER, "foobar" },
-			{ property::OFFSETS, offsets},
-			{ property::EVENT_BUFFER_SIZE, 64 }
-		});
-
-		REQUIRE_THAT(cfg.consumer(), Catch::Equals("foobar"));
-		REQUIRE_THAT(cfg.offsets(), Catch::Equals(offsets));
-		REQUIRE(cfg.event_buffer_size() == 64);
-	}
-
-	SECTION("invalid default value types passed to constructor")
-	{
-		REQUIRE_THROWS_AS(::gpiod::request_config({
-			{ property::CONSUMER, 42 }
-		}), ::std::invalid_argument);
-
-		REQUIRE_THROWS_AS(::gpiod::request_config({
-			{ property::OFFSETS, 42 }
-		}), ::std::invalid_argument);
-
-		REQUIRE_THROWS_AS(::gpiod::request_config({
-			{ property::EVENT_BUFFER_SIZE, "foobar" }
-		}), ::std::invalid_argument);
-	}
 }
 
 TEST_CASE("request_config can be moved", "[request-config]")
 {
-	offsets offsets({ 0, 1, 2, 3 });
+	::gpiod::request_config cfg;
 
-	::gpiod::request_config cfg({
-		{ property::CONSUMER, "foobar" },
-		{ property::OFFSETS, offsets },
-		{ property::EVENT_BUFFER_SIZE, 64 }
-	});
+	cfg.set_consumer("foobar").set_event_buffer_size(64);
 
 	SECTION("move constructor works")
 	{
 		auto moved(::std::move(cfg));
 		REQUIRE_THAT(moved.consumer(), Catch::Equals("foobar"));
-		REQUIRE_THAT(moved.offsets(), Catch::Equals(offsets));
 		REQUIRE(moved.event_buffer_size() == 64);
 	}
 
@@ -81,7 +44,6 @@ TEST_CASE("request_config can be moved", "[request-config]")
 		moved = ::std::move(cfg);
 
 		REQUIRE_THAT(moved.consumer(), Catch::Equals("foobar"));
-		REQUIRE_THAT(moved.offsets(), Catch::Equals(offsets));
 		REQUIRE(moved.event_buffer_size() == 64);
 	}
 }
@@ -96,13 +58,6 @@ TEST_CASE("request_config mutators work", "[request-config]")
 		REQUIRE_THAT(cfg.consumer(), Catch::Equals("foobar"));
 	}
 
-	SECTION("set offsets")
-	{
-		offsets offsets({ 3, 1, 2, 7, 5 });
-		cfg.set_offsets(offsets);
-		REQUIRE_THAT(cfg.offsets(), Catch::Equals(offsets));
-	}
-
 	SECTION("set event_buffer_size")
 	{
 		cfg.set_event_buffer_size(128);
@@ -110,44 +65,17 @@ TEST_CASE("request_config mutators work", "[request-config]")
 	}
 }
 
-TEST_CASE("request_config generic property setting works", "[request-config]")
+TEST_CASE("request_config stream insertion operator works", "[request-config]")
 {
 	::gpiod::request_config cfg;
 
-	SECTION("set consumer")
-	{
-		cfg.set_property(property::CONSUMER, "foobar");
-		REQUIRE_THAT(cfg.consumer(), Catch::Equals("foobar"));
-	}
-
-	SECTION("set offsets")
-	{
-		offsets offsets({ 3, 1, 2, 7, 5 });
-		cfg.set_property(property::OFFSETS, offsets);
-		REQUIRE_THAT(cfg.offsets(), Catch::Equals(offsets));
-	}
-
-	SECTION("set event_buffer_size")
-	{
-		cfg.set_property(property::EVENT_BUFFER_SIZE, 128);
-		REQUIRE(cfg.event_buffer_size() == 128);
-	}
-}
-
-TEST_CASE("request_config stream insertion operator works", "[request-config]")
-{
-	::gpiod::request_config cfg({
-		{ property::CONSUMER, "foobar" },
-		{ property::OFFSETS, offsets({ 0, 1, 2, 3 })},
-		{ property::EVENT_BUFFER_SIZE, 32 }
-	});
+	cfg.set_consumer("foobar").set_event_buffer_size(32);
 
 	::std::stringstream buf;
 
 	buf << cfg;
 
-	::std::string expected("gpiod::request_config(consumer='foobar', num_offsets=4, "
-			       "offsets=(gpiod::offsets(0, 1, 2, 3)), event_buffer_size=32)");
+	::std::string expected("gpiod::request_config(consumer='foobar', event_buffer_size=32)");
 
 	REQUIRE_THAT(buf.str(), Catch::Equals(expected));
 }
diff --git a/include/gpiod.h b/include/gpiod.h
index d8b5f39..b60a177 100644
--- a/include/gpiod.h
+++ b/include/gpiod.h
@@ -43,6 +43,7 @@ extern "C" {
 struct gpiod_chip;
 struct gpiod_chip_info;
 struct gpiod_line_info;
+struct gpiod_line_settings;
 struct gpiod_line_config;
 struct gpiod_request_config;
 struct gpiod_line_request;
@@ -167,12 +168,10 @@ int gpiod_chip_get_line_offset_from_name(struct gpiod_chip *chip,
 /**
  * @brief Request a set of lines for exclusive usage.
  * @param chip GPIO chip object.
- * @param req_cfg Request config object.
+ * @param req_cfg Request config object. Can be NULL for default settings.
  * @param line_cfg Line config object.
  * @return New line request object or NULL if an error occurred. The request
  *	   must be released by the caller using ::gpiod_line_request_release.
- * @note Line configuration overrides for lines that are not requested are
- *	 silently ignored.
  */
 struct gpiod_line_request *
 gpiod_chip_request_lines(struct gpiod_chip *chip,
@@ -229,7 +228,7 @@ size_t gpiod_chip_info_get_num_lines(struct gpiod_chip_info *info);
 /**
  * @}
  *
- * @defgroup line_settings Line definitions
+ * @defgroup line_defs Line definitions
  * @{
  *
  * These defines are used across the API.
@@ -514,584 +513,258 @@ gpiod_info_event_get_line_info(struct gpiod_info_event *event);
 /**
  * @}
  *
- * @defgroup line_config Line configuration objects
+ * @defgroup line_settings Line settings objects
  * @{
  *
- * Functions for manipulating line configuration objects.
- *
- * The line-config object contains the configuration for lines that can be
- * used in two cases:
- *  - when making a line request
- *  - when reconfiguring a set of already requested lines.
- *
- * A new line-config object is instantiated with a set of sane defaults
- * for all supported configuration settings. Those defaults can be modified by
- * the caller. Default values can be overridden by applying different values
- * for specific lines. When making a request or reconfiguring an existing one,
- * the overridden settings for specific lines take precedance. For lines
- * without an override the requested default settings are used.
- *
- * For every setting there are two mutators (one setting the default and one
- * for the per-line override), two getters (one for reading the global
- * default and one for retrieving the effective value for the line),
- * a function for testing if a setting is overridden for the line
- * and finally a function for clearing the overrides (per line).
- *
- * The mutators don't return errors. If the set of options is too complex to
- * be translated into kernel uAPI structures then an error will be returned at
- * the time of the request or reconfiguration. If an invalid value was passed
- * to any of the mutators then the default value will be silently used instead.
- *
- * Operating on lines in struct line_config has no immediate effect on real
- * GPIOs, it only manipulates the config object in memory.  Those changes are
- * only applied to the hardware at the time of the request or reconfiguration.
+ * Functions for manipulating line settings objects.
  *
- * Overrides for lines that don't end up being requested are silently ignored
- * both in ::gpiod_chip_request_lines as well as in
- * ::gpiod_line_request_reconfigure_lines.
+ * Line settings object contains a set of line properties that can be used
+ * when requesting lines or reconfiguring an existing request.
  *
- * In cases where all requested lines are using the one configuration, the
- * line overrides can be entirely ignored when preparing the configuration.
+ * Mutators in general can only fail if the new property value is invalid. The
+ * return values can be safely ignored - the object remains valid even after
+ * a mutator fails and simply uses the sane default appropriate for given
+ * property.
  */
 
 /**
- * @brief Create a new line config object.
- * @return New line config object or NULL on error.
+ * @brief Create a new line settings object.
+ * @return New line settings object or NULL on error.
  */
-struct gpiod_line_config *gpiod_line_config_new(void);
+struct gpiod_line_settings *gpiod_line_settings_new(void);
 
 /**
- * @brief Free the line config object and release all associated resources.
- * @param config Line config object to free.
+ * @brief Free the line settings object and release all associated resources.
+ * @param settings Line settings object.
  */
-void gpiod_line_config_free(struct gpiod_line_config *config);
+void gpiod_line_settings_free(struct gpiod_line_settings *settings);
 
 /**
- * @brief Reset the line config object.
- * @param config Line config object to free.
- *
- * Resets the entire configuration stored in the object. This is useful if
- * the user wants to reuse the object without reallocating it.
+ * @brief Reset the line settings object to its default values.
+ * @param settings Line settings object.
  */
-void gpiod_line_config_reset(struct gpiod_line_config *config);
+void gpiod_line_settings_reset(struct gpiod_line_settings *settings);
 
 /**
- * @brief Set the default line direction.
- * @param config Line config object.
- * @param direction New direction.
+ * @brief Copy the line settings object.
+ * @param settings Line settings object to copy.
+ * @return New line settings object that must be freed using
+ *         ::gpiod_line_settings_free or NULL on failure.
  */
-void gpiod_line_config_set_direction_default(struct gpiod_line_config *config,
-					     int direction);
+struct gpiod_line_settings *
+gpiod_line_settings_copy(struct gpiod_line_settings *settings);
 
 /**
- * @brief Set the direction override for a line.
- * @param config Line config object.
+ * @brief Set direction.
+ * @param settings Line settings object.
  * @param direction New direction.
- * @param offset The offset of the line for which to set the override.
- */
-void gpiod_line_config_set_direction_override(struct gpiod_line_config *config,
-					      int direction,
-					      unsigned int offset);
-
-/**
- * @brief Clear the direction override for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to clear the override.
- * @note Does nothing if no override is set for the line.
- */
-void
-gpiod_line_config_clear_direction_override(struct gpiod_line_config *config,
-					   unsigned int offset);
-
-/**
- * @brief Check if the direction is overridden for a line.
- * @param config Line config object.
- * @param offset The offset of the line to check for the override.
- * @return True if direction is overridden on the line, false otherwise.
- */
-bool gpiod_line_config_direction_is_overridden(struct gpiod_line_config *config,
-					       unsigned int offset);
-
-/**
- * @brief Get the default direction setting.
- * @param config Line config object.
- * @return Direction setting used for any non-overridden line.
- */
-int gpiod_line_config_get_direction_default(struct gpiod_line_config *config);
-
-/**
- * @brief Get the direction setting for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to read the direction.
- * @return Direction setting for the line if the config object were used
- *	   in a request.
- */
-int gpiod_line_config_get_direction_offset(struct gpiod_line_config *config,
-					   unsigned int offset);
-
-/**
- * @brief Set the default edge event detection.
- * @param config Line config object.
- * @param edge Type of edge events to detect.
- */
-void
-gpiod_line_config_set_edge_detection_default(struct gpiod_line_config *config,
-					     int edge);
-
-/**
- * @brief Set the edge detection override for a line.
- * @param config Line config object.
- * @param edge Type of edge events to detect.
- * @param offset The offset of the line for which to set the override.
+ * @return 0 on success, -1 on error.
  */
-void
-gpiod_line_config_set_edge_detection_override(struct gpiod_line_config *config,
-					      int edge, unsigned int offset);
+int gpiod_line_settings_set_direction(struct gpiod_line_settings *settings,
+				      int direction);
 
 /**
- * @brief Clear the edge detection override for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to clear the override.
- * @note Does nothing if no override is set for the line.
+ * @brief Get direction.
+ * @param settings Line settings object.
+ * @return Current direction.
  */
-void
-gpiod_line_config_clear_edge_detection_override(
-			struct gpiod_line_config *config, unsigned int offset);
+int gpiod_line_settings_get_direction(struct gpiod_line_settings *settings);
 
 /**
- * @brief Check if the edge detection setting is overridden for a line.
- * @param config Line config object.
- * @param offset The offset of the line to check for the override.
- * @return True if edge detection is overridden for the line, false otherwise.
- */
-bool
-gpiod_line_config_edge_detection_is_overridden(struct gpiod_line_config *config,
-					       unsigned int offset);
-
-/**
- * @brief Get the default edge detection setting.
- * @param config Line config object.
- * @return Edge detection setting used for any non-overridden line.
+ * @brief Set edge detection.
+ * @param settings Line settings object.
+ * @param edge New edge detection setting.
+ * @return 0 on success, -1 on failure.
  */
-int
-gpiod_line_config_get_edge_detection_default(struct gpiod_line_config *config);
+int gpiod_line_settings_set_edge_detection(struct gpiod_line_settings *settings,
+					   int edge);
 
 /**
- * @brief Get the edge event detection setting for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to read the edge event detection
- *		 setting.
- * @return Edge event detection setting for the line if the config object
- *	   were used in a request.
+ * @brief Get edge detection.
+ * @param settings Line settings object.
+ * @return Current edge detection setting.
  */
 int
-gpiod_line_config_get_edge_detection_offset(struct gpiod_line_config *config,
-					    unsigned int offset);
+gpiod_line_settings_get_edge_detection(struct gpiod_line_settings *settings);
 
 /**
- * @brief Set the default bias setting.
- * @param config Line config object.
+ * @brief Set bias.
+ * @param settings Line settings object.
  * @param bias New bias.
+ * @return 0 on success, -1 on failure.
  */
-void gpiod_line_config_set_bias_default(struct gpiod_line_config *config,
-					int bias);
-
-/**
- * @brief Set the bias override for a line.
- * @param config Line config object.
- * @param bias New bias setting.
- * @param offset The offset of the line for which to set the override.
- */
-void gpiod_line_config_set_bias_override(struct gpiod_line_config *config,
-					 int bias, unsigned int offset);
-
-/**
- * @brief Clear the bias override for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to clear the override.
- * @note Does nothing if no override is set for the line.
- */
-void gpiod_line_config_clear_bias_override(struct gpiod_line_config *config,
-					   unsigned int offset);
-
-/**
- * @brief Check if the bias setting is overridden for a line.
- * @param config Line config object.
- * @param offset The offset of the line to check for the override.
- * @return True if bias is overridden for the line, false otherwise.
- */
-bool gpiod_line_config_bias_is_overridden(struct gpiod_line_config *config,
-					  unsigned int offset);
-/**
- * @brief Get the default bias setting.
- * @param config Line config object.
- * @return Bias setting used for any non-overridden line.
- */
-int gpiod_line_config_get_bias_default(struct gpiod_line_config *config);
+int gpiod_line_settings_set_bias(struct gpiod_line_settings *settings,
+				 int bias);
 
 /**
- * @brief Get the bias setting for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to read the bias setting.
- * @return Bias setting used for the line if the config object were used
- *	   in a request.
+ * @brief Get bias.
+ * @param settings Line settings object.
+ * @return Current bias setting.
  */
-int gpiod_line_config_get_bias_offset(struct gpiod_line_config *config,
-				      unsigned int offset);
+int gpiod_line_settings_get_bias(struct gpiod_line_settings *settings);
 
 /**
- * @brief Set the default drive setting.
- * @param config Line config object.
- * @param drive New drive.
- */
-void gpiod_line_config_set_drive_default(struct gpiod_line_config *config,
-					 int drive);
-
-/**
- * @brief Set the drive override for a line.
- * @param config Line config object.
+ * @brief Set drive.
+ * @param settings Line settings object.
  * @param drive New drive setting.
- * @param offset The offset of the line for which to set the override.
- */
-void gpiod_line_config_set_drive_override(struct gpiod_line_config *config,
-					  int drive, unsigned int offset);
-
-/**
- * @brief Clear the drive override for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to clear the override.
- * @note Does nothing if no override is set for the line.
- */
-void gpiod_line_config_clear_drive_override(struct gpiod_line_config *config,
-					    unsigned int offset);
-
-/**
- * @brief Check if the drive setting is overridden for a line.
- * @param config Line config object.
- * @param offset The offset of the line to check for the override.
- * @return True if drive is overridden for the line, false otherwise.
- */
-bool gpiod_line_config_drive_is_overridden(struct gpiod_line_config *config,
-					   unsigned int offset);
-
-/**
- * @brief Get the default drive setting.
- * @param config Line config object.
- * @return Drive setting for any non-overridden line.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_config_get_drive_default(struct gpiod_line_config *config);
+int gpiod_line_settings_set_drive(struct gpiod_line_settings *settings,
+				  int drive);
 
 /**
- * @brief Get the drive setting for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to read the drive setting.
- * @return Drive setting for the line if the config object were used in a
- *	   request.
+ * @brief Get drive.
+ * @param settings Line settings object.
+ * @return Current drive setting.
  */
-int gpiod_line_config_get_drive_offset(struct gpiod_line_config *config,
-				       unsigned int offset);
+int gpiod_line_settings_get_drive(struct gpiod_line_settings *settings);
 
 /**
- * @brief Set the default active-low setting.
- * @param config Line config object.
+ * @brief Set active-low setting.
+ * @param settings Line settings object.
  * @param active_low New active-low setting.
  */
-void gpiod_line_config_set_active_low_default(struct gpiod_line_config *config,
-					      bool active_low);
-
-/**
- * @brief Override the active-low setting for a line.
- * @param config Line config object.
- * @param active_low New active-low setting.
- * @param offset The offset of the line for which to set the override.
- */
-void gpiod_line_config_set_active_low_override(struct gpiod_line_config *config,
-					       bool active_low,
-					       unsigned int offset);
-
-/**
- * @brief Clear the active-low override for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to clear the override.
- * @note Does nothing if no override is set for the line.
- */
-void
-gpiod_line_config_clear_active_low_override(struct gpiod_line_config *config,
-					    unsigned int offset);
-
-/**
- * @brief Check if the active-low setting is overridden for a line.
- * @param config Line config object.
- * @param offset The offset of the line to check for the override.
- * @return True if active-low is overridden for the line, false otherwise.
- */
-bool
-gpiod_line_config_active_low_is_overridden(struct gpiod_line_config *config,
-					   unsigned int offset);
-
-/**
- * @brief Check if active-low is the default setting.
- * @param config Line config object.
- * @return Active-low setting for any non-overridden line.
- */
-bool gpiod_line_config_get_active_low_default(struct gpiod_line_config *config);
+void gpiod_line_settings_set_active_low(struct gpiod_line_settings *settings,
+					bool active_low);
 
 /**
- * @brief Check if a line is configured as active-low.
- * @param config Line config object.
- * @param offset The offset of the line for which to read the active-low setting.
- * @return Active-low setting for the line if the config object were used in
- *	   a request.
+ * @brief Get active-low setting.
+ * @param settings Line settings object.
+ * @return True if active-low is enabled, false otherwise.
  */
-bool gpiod_line_config_get_active_low_offset(struct gpiod_line_config *config,
-					     unsigned int offset);
+bool gpiod_line_settings_get_active_low(struct gpiod_line_settings *settings);
 
 /**
- * @brief Set the default debounce period.
- * @param config Line config object.
- * @param period New debounce period in microseconds. Disables debouncing if 0.
- * @note Debouncing is only useful on input lines with edge detection.
- *	 Its purpose is to filter spurious events due to noise during the
- *	 edge transition.  It has no effect on normal get or set operations.
- */
-void gpiod_line_config_set_debounce_period_us_default(
-		struct gpiod_line_config *config, unsigned long period);
-
-/**
- * @brief Override the debounce period setting for a line.
- * @param config Line config object.
+ * @brief Set debounce period.
+ * @param settings Line settings object.
  * @param period New debounce period in microseconds.
- * @param offset The offset of the line for which to set the override.
  */
 void
-gpiod_line_config_set_debounce_period_us_override(
-					struct gpiod_line_config *config,
-					unsigned long period,
-					unsigned int offset);
+gpiod_line_settings_set_debounce_period_us(struct gpiod_line_settings *settings,
+					   unsigned long period);
 
 /**
- * @brief Clear the debounce period override for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to clear the override.
- * @note Does nothing if no override is set for the line.
- */
-void gpiod_line_config_clear_debounce_period_us_override(
-					struct gpiod_line_config *config,
-					unsigned int offset);
-
-/**
- * @brief Check if the debounce period setting is overridden for a line.
- * @param config Line config object.
- * @param offset The offset of the line to check for the override.
- * @return True if debounce period is overridden for the line, false
- *	   otherwise.
- */
-bool gpiod_line_config_debounce_period_us_is_overridden(
-					struct gpiod_line_config *config,
-					unsigned int offset);
-
-/**
- * @brief Get the default debounce period.
- * @param config Line config object.
- * @return Debounce period for any non-overridden line.
- *	   Measured in microseconds.
- *	   0 if debouncing is disabled.
- */
-unsigned long gpiod_line_config_get_debounce_period_us_default(
-					struct gpiod_line_config *config);
-
-/**
- * @brief Get the debounce period for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to read the debounce period.
- * @return Debounce period for the line if the config object were used in a
- *	   request.
- *	   Measured in microseconds.
- *	   0 if debouncing is disabled.
+ * @brief Get debounce period.
+ * @param settings Line settings object.
+ * @return Current debounce period in microseconds.
  */
 unsigned long
-gpiod_line_config_get_debounce_period_us_offset(
-			struct gpiod_line_config *config, unsigned int offset);
-
-/**
- * @brief Set the default event timestamp clock.
- * @param config Line config object.
- * @param clock New clock to use.
- */
-void gpiod_line_config_set_event_clock_default(struct gpiod_line_config *config,
-					       int clock);
-
-/**
- * @brief Override the event clock setting for a line.
- * @param config Line config object.
- * @param clock New event clock to use.
- * @param offset The offset of the line for which to set the override.
- */
-void
-gpiod_line_config_set_event_clock_override(struct gpiod_line_config *config,
-					   int clock, unsigned int offset);
+gpiod_line_settings_get_debounce_period_us(
+		struct gpiod_line_settings *settings);
 
 /**
- * @brief Clear the event clock override for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to clear the override.
- * @note Does nothing if no override is set for the line.
+ * @brief Set event clock.
+ * @param settings Line settings object.
+ * @param event_clock New event clock.
+ * @return 0 on success, -1 on failure.
  */
-void
-gpiod_line_config_clear_event_clock_override(struct gpiod_line_config *config,
-					     unsigned int offset);
+int gpiod_line_settings_set_event_clock(struct gpiod_line_settings *settings,
+					int event_clock);
 
 /**
- * @brief Check if the event clock setting is overridden for a line.
- * @param config Line config object.
- * @param offset The offset of the line to check for the override.
- * @return True if event clock period is overridden for the line, false
- *	   otherwise.
+ * @brief Get event clock setting.
+ * @param settings Line settings object.
+ * @return Current event clock setting.
  */
-bool
-gpiod_line_config_event_clock_is_overridden(struct gpiod_line_config *config,
-					    unsigned int offset);
+int gpiod_line_settings_get_event_clock(struct gpiod_line_settings *settings);
 
 /**
- * @brief Get the default event clock setting.
- * @param config Line config object.
- * @return Event clock setting for any non-overridden line.
+ * @brief Set the output value.
+ * @param settings Line settings object.
+ * @param value New output value.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_config_get_event_clock_default(struct gpiod_line_config *config);
+int gpiod_line_settings_set_output_value(struct gpiod_line_settings *settings,
+					 int value);
 
 /**
- * @brief Get the event clock setting for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to read the event clock setting.
- * @return Event clock setting for the line if the config object were used in a
- *	   request.
+ * @brief Get the output value.
+ * @param settings Line settings object.
+ * @return Current output value.
  */
-int gpiod_line_config_get_event_clock_offset(struct gpiod_line_config *config,
-					     unsigned int offset);
+int gpiod_line_settings_get_output_value(struct gpiod_line_settings *settings);
 
-/**
- * @brief Set the default output value.
- * @param config Line config object.
- * @param value New value.
+/*
+ * @}
  *
- * The default output value applies to all non-overridden output lines.
- * It does not apply to input lines or overridden lines.
- */
-void
-gpiod_line_config_set_output_value_default(struct gpiod_line_config *config,
-					   int value);
-
-/**
- * @brief Override the output value for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to override the output value.
- * @param value Output value to set.
- */
-void
-gpiod_line_config_set_output_value_override(struct gpiod_line_config *config,
-					    int value, unsigned int offset);
-
-/**
- * @brief Override the output values for multiple lines.
- * @param config Line config object.
- * @param num_values Number of lines for which to override values.
- * @param offsets Array of offsets identifying the lines for which to override
- *		  values,  containing \p num_values entries.
- * @param values Array of output values corresponding to the lines identified in
- *		 \p offsets, also containing \p num_values entries.
+ * @defgroup line_config Line configuration objects
+ * @{
+ *
+ * Functions for manipulating line configuration objects.
+ *
+ * The line-config object contains the configuration for lines that can be
+ * used in two cases:
+ *  - when making a line request
+ *  - when reconfiguring a set of already requested lines.
+ *
+ * A new line-config object is empty. Using it in a request will lead to an
+ * error. In order to a line-config to become useful, it needs to be assigned
+ * at least one offset-to-settings mapping by calling
+ * ::gpiod_line_config_add_line_settings.
+ *
+ * When calling ::gpiod_chip_request_lines, the library will request all
+ * offsets that were assigned settings in the order that they were assigned.
+ * If any of the offsets was duplicated, the last one will take precedence.
  */
-void gpiod_line_config_set_output_values(struct gpiod_line_config *config,
-					 size_t num_values,
-					 const unsigned int *offsets,
-					 const int *values);
 
 /**
- * @brief Clear the output value override for a line.
- * @param config Line config object.
- * @param offset The offset of the line for which to clear the override.
- * @note Does nothing if no override is set for the line.
+ * @brief Create a new line config object.
+ * @return New line config object or NULL on error.
  */
-void
-gpiod_line_config_clear_output_value_override(struct gpiod_line_config *config,
-					      unsigned int offset);
+struct gpiod_line_config *gpiod_line_config_new(void);
 
 /**
- * @brief Check if the output value is overridden for a line.
- * @param config Line config object.
- * @param offset The offset of the line to check for the override.
- * @return True if output value is overridden for the line, false otherwise.
+ * @brief Free the line config object and release all associated resources.
+ * @param config Line config object to free.
  */
-bool
-gpiod_line_config_output_value_is_overridden(struct gpiod_line_config *config,
-					     unsigned int offset);
+void gpiod_line_config_free(struct gpiod_line_config *config);
 
 /**
- * @brief Get the default output value.
- * @param config Line config object.
- * @return Output value for any non-overridden line.
+ * @brief Reset the line config object.
+ * @param config Line config object to free.
+ *
+ * Resets the entire configuration stored in the object. This is useful if
+ * the user wants to reuse the object without reallocating it.
  */
-int
-gpiod_line_config_get_output_value_default(struct gpiod_line_config *config);
+void gpiod_line_config_reset(struct gpiod_line_config *config);
 
 /**
- * @brief Get the configured output value for a line.
+ * @brief Add line settings for a set of offsets.
  * @param config Line config object.
- * @param offset Line offset for which to read the value.
- * @return Output value for the line if the config object were used in a
- *	   request.
- */
-int gpiod_line_config_get_output_value_offset(struct gpiod_line_config *config,
-					      unsigned int offset);
-
-/**
- * @brief List of properties that can be stored in a line_config object.
- *
- * Used when retrieving the overrides.
+ * @param offsets Array of offsets for which to apply the settings.
+ * @param num_offsets Number of offsets stored in the offsets array.
+ * @param settings Line settings to apply.
+ * @return 0 on success, -1 on failure.
  */
-enum {
-	GPIOD_LINE_CONFIG_PROP_DIRECTION = 1,
-	/**< Line direction. */
-	GPIOD_LINE_CONFIG_PROP_EDGE_DETECTION,
-	/**< Edge detection. */
-	GPIOD_LINE_CONFIG_PROP_BIAS,
-	/**< Bias. */
-	GPIOD_LINE_CONFIG_PROP_DRIVE,
-	/**< Drive. */
-	GPIOD_LINE_CONFIG_PROP_ACTIVE_LOW,
-	/**< Active-low setting. */
-	GPIOD_LINE_CONFIG_PROP_DEBOUNCE_PERIOD_US,
-	/** Debounce period. */
-	GPIOD_LINE_CONFIG_PROP_EVENT_CLOCK,
-	/**< Event clock type. */
-	GPIOD_LINE_CONFIG_PROP_OUTPUT_VALUE,
-	/**< Output value. */
-};
+int gpiod_line_config_add_line_settings(struct gpiod_line_config *config,
+					const unsigned int *offsets,
+					size_t num_offsets,
+					struct gpiod_line_settings *settings);
 
 /**
- * @brief Get the total number of overridden settings stored in the line config
- *	  object.
+ * @brief Get line settings for offset.
  * @param config Line config object.
- * @return Number of individual overridden settings.
+ * @param offset Offset for which to get line settings.
+ * @return New line settings object (must be freed by the caller) or NULL on
+ *         error.
  */
-size_t gpiod_line_config_get_num_overrides(struct gpiod_line_config *config);
+struct gpiod_line_settings *
+gpiod_line_config_get_line_settings(struct gpiod_line_config *config,
+				    unsigned int offset);
 
 /**
- * @brief Get the list of overridden offsets and the corresponding types of
- *	  overridden settings.
+ * @brief Get configured offsets.
  * @param config Line config object.
- * @param offsets Array to store the overidden offsets. Must be sized to hold
- *		  the number of unsigned integers returned by
- *		  ::gpiod_line_config_get_num_overrides.
- * @param props Array to store the types of overridden settings. Must be sized
- *		to hold the number of integers returned by
- *		::gpiod_line_config_get_num_overrides.
- *
- * The overridden (offset, prop) pairs are stored in the \p offsets and
- * \p props arrays, with the pairs having the same index.
+ * @param num_offsets Pointer to a variable in which the number of line offsets
+ *                    will be stored.
+ * @param offsets Pointer to a pointer which will be set to point to an array
+ *                containing the configured offsets. The array will be allocated
+ *                using malloc() and must be freed using free().
+ * @return 0 on success, -1 on failure.
  */
-void
-gpiod_line_config_get_overrides(struct gpiod_line_config *config,
-				unsigned int *offsets, int *props);
+int gpiod_line_config_get_offsets(struct gpiod_line_config *config,
+				  size_t *num_offsets,
+				  unsigned int **offsets);
 
 /**
  * @}
@@ -1102,9 +775,9 @@ gpiod_line_config_get_overrides(struct gpiod_line_config *config,
  * Functions for manipulating request configuration objects.
  *
  * Request config objects are used to pass a set of options to the kernel at
- * the time of the line request. Similarly to the line-config - the mutators
- * don't return error values. If the values are invalid, in general they are
- * silently adjusted to acceptable ranges.
+ * the time of the line request. The mutators don't return error values. If the
+ * values are invalid, in general they are silently adjusted to acceptable
+ * ranges.
  */
 
 /**
@@ -1137,35 +810,6 @@ void gpiod_request_config_set_consumer(struct gpiod_request_config *config,
 const char *
 gpiod_request_config_get_consumer(struct gpiod_request_config *config);
 
-/**
- * @brief Set the offsets of the lines to be requested.
- * @param config Request config object.
- * @param num_offsets Number of offsets to set.
- * @param offsets Array of offsets, containing \p num_offsets entries.
- * @note If too many offsets were specified, the offsets above the limit
- *       accepted by the kernel (64 lines) are silently dropped.
- */
-void gpiod_request_config_set_offsets(struct gpiod_request_config *config,
-				      size_t num_offsets,
-				      const unsigned int *offsets);
-
-/**
- * @brief Get the number of offsets configured in this request config.
- * @param config Request config object.
- * @return Number of line offsets in this request config.
- */
-size_t
-gpiod_request_config_get_num_offsets(struct gpiod_request_config *config);
-
-/**
- * @brief Get the offsets of lines in the request config.
- * @param config Request config object.
- * @param offsets Array to store offsets. Must be sized to hold the number of
- *		  lines returned by ::gpiod_request_config_get_num_offsets.
- */
-void gpiod_request_config_get_offsets(struct gpiod_request_config *config,
-				      unsigned int *offsets);
-
 /**
  * @brief Set the size of the kernel event buffer for the request.
  * @param config Request config object.
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 1bd2b2e..dd90abd 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -11,6 +11,7 @@ libgpiod_la_SOURCES =	chip.c \
 			line-config.c \
 			line-info.c \
 			line-request.c \
+			line-settings.c \
 			misc.c \
 			request-config.c \
 			uapi/gpio.h
diff --git a/lib/chip.c b/lib/chip.c
index 4158533..e0fb309 100644
--- a/lib/chip.c
+++ b/lib/chip.c
@@ -192,12 +192,10 @@ gpiod_chip_request_lines(struct gpiod_chip *chip,
 
 	memset(&uapi_req, 0, sizeof(uapi_req));
 
-	ret = gpiod_request_config_to_uapi(req_cfg, &uapi_req);
-	if (ret)
-		return NULL;
+	if (req_cfg)
+		gpiod_request_config_to_uapi(req_cfg, &uapi_req);
 
-	ret = gpiod_line_config_to_uapi(line_cfg, &uapi_req.config,
-					uapi_req.num_lines, uapi_req.offsets);
+	ret = gpiod_line_config_to_uapi(line_cfg, &uapi_req);
 	if (ret)
 		return NULL;
 
diff --git a/lib/internal.h b/lib/internal.h
index 97731f9..eef70aa 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -13,9 +13,6 @@
 /* For internal library use only. */
 
 #define GPIOD_API	__attribute__((visibility("default")))
-#define GPIOD_PACKED	__attribute__((packed))
-#define GPIOD_UNUSED	__attribute__((unused))
-
 #define GPIOD_BIT(nr)	(1UL << (nr))
 
 bool gpiod_check_gpiochip_device(const char *path, bool set_errno);
@@ -24,12 +21,10 @@ struct gpiod_chip_info *
 gpiod_chip_info_from_uapi(struct gpiochip_info *uapi_info);
 struct gpiod_line_info *
 gpiod_line_info_from_uapi(struct gpio_v2_line_info *uapi_info);
-int gpiod_request_config_to_uapi(struct gpiod_request_config *config,
-				 struct gpio_v2_line_request *uapi_req);
+void gpiod_request_config_to_uapi(struct gpiod_request_config *config,
+				  struct gpio_v2_line_request *uapi_req);
 int gpiod_line_config_to_uapi(struct gpiod_line_config *config,
-			      struct gpio_v2_line_config *uapi_cfg,
-			      unsigned int num_lines,
-			      const unsigned int *offsets);
+			      struct gpio_v2_line_request *uapi_cfg);
 struct gpiod_line_request *
 gpiod_line_request_from_uapi(struct gpio_v2_line_request *uapi_req);
 int gpiod_edge_event_buffer_read_fd(int fd, struct gpiod_edge_event_buffer *buffer,
diff --git a/lib/line-config.c b/lib/line-config.c
index 979b4c5..114d40c 100644
--- a/lib/line-config.c
+++ b/lib/line-config.c
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
 
 #include <errno.h>
 #include <gpiod.h>
@@ -8,78 +8,24 @@
 
 #include "internal.h"
 
-struct base_config {
-	unsigned int direction : 2;
-	unsigned int edge : 3;
-	unsigned int drive : 2;
-	unsigned int bias : 3;
-	bool active_low : 1;
-	unsigned int clock : 2;
-	unsigned long debounce_period_us;
-	unsigned int value : 1;
-} GPIOD_PACKED;
-
-#define OVERRIDE_FLAG_DIRECTION		GPIOD_BIT(0)
-#define OVERRIDE_FLAG_EDGE		GPIOD_BIT(1)
-#define OVERRIDE_FLAG_DRIVE		GPIOD_BIT(2)
-#define OVERRIDE_FLAG_BIAS		GPIOD_BIT(3)
-#define OVERRIDE_FLAG_ACTIVE_LOW	GPIOD_BIT(4)
-#define OVERRIDE_FLAG_CLOCK		GPIOD_BIT(5)
-#define OVERRIDE_FLAG_DEBOUNCE_PERIOD	GPIOD_BIT(6)
-#define OVERRIDE_FLAG_OUTPUT_VALUE	GPIOD_BIT(7)
-
-static const int override_flag_list[] = {
-	OVERRIDE_FLAG_DIRECTION,
-	OVERRIDE_FLAG_EDGE,
-	OVERRIDE_FLAG_BIAS,
-	OVERRIDE_FLAG_DRIVE,
-	OVERRIDE_FLAG_ACTIVE_LOW,
-	OVERRIDE_FLAG_DEBOUNCE_PERIOD,
-	OVERRIDE_FLAG_CLOCK,
-	OVERRIDE_FLAG_OUTPUT_VALUE
-};
+#define LINES_MAX (GPIO_V2_LINES_MAX)
 
-#define NUM_OVERRIDE_FLAGS		8
-#define NUM_OVERRIDES_MAX		(GPIO_V2_LINES_MAX)
+struct settings_node {
+	struct settings_node *next;
+	struct gpiod_line_settings *settings;
+};
 
-/*
- * Config overriding the defaults for a single line offset. Only flagged
- * settings are actually overriden for a line.
- */
-struct override_config {
-	struct base_config base;
+struct per_line_config {
 	unsigned int offset;
-	unsigned int override_flags : 8;
-} GPIOD_PACKED;
+	struct settings_node *node;
+};
 
 struct gpiod_line_config {
-	bool too_complex;
-	struct base_config defaults;
-	struct override_config overrides[NUM_OVERRIDES_MAX];
+	struct per_line_config line_configs[LINES_MAX];
+	size_t num_configs;
+	struct settings_node *sref_list;
 };
 
-static void init_base_config(struct base_config *config)
-{
-	config->direction = GPIOD_LINE_DIRECTION_AS_IS;
-	config->edge = GPIOD_LINE_EDGE_NONE;
-	config->bias = GPIOD_LINE_BIAS_AS_IS;
-	config->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
-	config->active_low = false;
-	config->clock = GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
-	config->debounce_period_us = 0;
-}
-
-static void init_override_config(struct override_config *override)
-{
-	override->override_flags = 0;
-	init_base_config(&override->base);
-}
-
-static bool override_used(struct override_config *override)
-{
-	return !!override->override_flags;
-}
-
 GPIOD_API struct gpiod_line_config *gpiod_line_config_new(void)
 {
 	struct gpiod_line_config *config;
@@ -88,737 +34,258 @@ GPIOD_API struct gpiod_line_config *gpiod_line_config_new(void)
 	if (!config)
 		return NULL;
 
-	gpiod_line_config_reset(config);
+	memset(config, 0, sizeof(*config));
 
 	return config;
 }
 
+static void free_refs(struct gpiod_line_config *config)
+{
+	struct settings_node *node, *tmp;
+
+	for (node = config->sref_list; node;) {
+		tmp = node->next;
+		gpiod_line_settings_free(node->settings);
+		free(node);
+		node = tmp;
+	}
+}
+
 GPIOD_API void gpiod_line_config_free(struct gpiod_line_config *config)
 {
+	free_refs(config);
 	free(config);
 }
 
 GPIOD_API void gpiod_line_config_reset(struct gpiod_line_config *config)
 {
-	size_t i;
-
+	free_refs(config);
 	memset(config, 0, sizeof(*config));
-	init_base_config(&config->defaults);
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++)
-		init_override_config(&config->overrides[i]);
 }
 
-static struct override_config *
-get_override_by_offset(struct gpiod_line_config *config, unsigned int offset)
+static struct per_line_config *
+find_config(struct gpiod_line_config *config, unsigned int offset)
 {
-	struct override_config *override;
+	struct per_line_config *per_line;
 	size_t i;
 
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-		override = &config->overrides[i];
+	for (i = 0; i < config->num_configs; i++) {
+		per_line = &config->line_configs[i];
 
-		if (override->offset == offset)
-			return override;
+		if (offset == per_line->offset)
+			return per_line;
 	}
 
-	return NULL;
+	return &config->line_configs[config->num_configs++];
 }
 
-static struct override_config *
-get_free_override(struct gpiod_line_config *config, unsigned int offset)
+GPIOD_API int
+gpiod_line_config_add_line_settings(struct gpiod_line_config *config,
+				    const unsigned int *offsets,
+				    size_t num_offsets,
+				    struct gpiod_line_settings *settings)
 {
-	struct override_config *override;
+	struct per_line_config *per_line;
+	struct settings_node *node;
 	size_t i;
 
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-		override = &config->overrides[i];
-
-		if (override->override_flags)
-			continue;
-
-		override->offset = offset;
-		return override;
+	if ((config->num_configs + num_offsets) > LINES_MAX) {
+		errno = E2BIG;
+		return -1;
 	}
 
-	/* No more free overrides. */
-	config->too_complex = true;
-	return NULL;
-}
-
-static struct override_config *
-get_override_config_for_writing(struct gpiod_line_config *config,
-				unsigned int offset)
-{
-	struct override_config *override;
-
-	if (config->too_complex)
-		return NULL;
+	node = malloc(sizeof(*node));
+	if (!node)
+		return -1;
 
-	override = get_override_by_offset(config, offset);
-	if (!override) {
-		override = get_free_override(config, offset);
-		if (!override)
-			return NULL;
+	if (!settings)
+		node->settings = gpiod_line_settings_new();
+	else
+		node->settings = gpiod_line_settings_copy(settings);
+	if (!node->settings) {
+		free(node);
+		return -1;
 	}
 
-	return override;
-}
-
-static struct base_config *
-get_base_config_for_reading(struct gpiod_line_config *config,
-			    unsigned int offset, unsigned int flag)
-{
-	struct override_config *override;
+	node->next = config->sref_list;
+	config->sref_list = node;
 
-	override = get_override_by_offset(config, offset);
-	if (override && (override->override_flags & flag))
-		return &override->base;
+	for (i = 0; i < num_offsets; i++) {
+		per_line = find_config(config, offsets[i]);
 
-	return &config->defaults;
-}
-
-static void clear_override(struct gpiod_line_config *config,
-			   unsigned int offset, int flag)
-{
-	struct override_config *override;
-
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return;
-
-	if (override->override_flags & flag) {
-		override->override_flags &= ~flag;
-
-		if (!override->override_flags)
-			init_override_config(override);
+		per_line->offset = offsets[i];
+		per_line->node = node;
 	}
-}
-
-static bool check_override(struct gpiod_line_config *config,
-			   unsigned int offset, int flag)
-{
-	struct override_config *override;
-
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return false;
 
-	return override->override_flags & flag;
-}
-
-static void set_direction(struct base_config *config, int direction)
-{
-	switch (direction) {
-	case GPIOD_LINE_DIRECTION_INPUT:
-	case GPIOD_LINE_DIRECTION_OUTPUT:
-	case GPIOD_LINE_DIRECTION_AS_IS:
-		config->direction = direction;
-		break;
-	default:
-		config->direction = GPIOD_LINE_DIRECTION_AS_IS;
-		break;
-	}
-}
-
-GPIOD_API void
-gpiod_line_config_set_direction_default(struct gpiod_line_config *config,
-					int direction)
-{
-	set_direction(&config->defaults, direction);
-}
-
-GPIOD_API void
-gpiod_line_config_set_direction_override(struct gpiod_line_config *config,
-				       int direction, unsigned int offset)
-{
-	struct override_config *override;
-
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return;
-
-	set_direction(&override->base, direction);
-	override->override_flags |= OVERRIDE_FLAG_DIRECTION;
-}
-
-GPIOD_API void
-gpiod_line_config_clear_direction_override(struct gpiod_line_config *config,
-					   unsigned int offset)
-{
-	clear_override(config, offset, OVERRIDE_FLAG_DIRECTION);
-}
-
-GPIOD_API bool
-gpiod_line_config_direction_is_overridden(struct gpiod_line_config *config,
-					 unsigned int offset)
-{
-	return check_override(config, offset, OVERRIDE_FLAG_DIRECTION);
-}
-
-GPIOD_API int
-gpiod_line_config_get_direction_default(struct gpiod_line_config *config)
-{
-	return config->defaults.direction;
+	return 0;
 }
 
-GPIOD_API int
-gpiod_line_config_get_direction_offset(struct gpiod_line_config *config,
-				       unsigned int offset)
+GPIOD_API struct gpiod_line_settings *
+gpiod_line_config_get_line_settings(struct gpiod_line_config *config,
+				    unsigned int offset)
 {
-	struct base_config *base;
-
-	base = get_base_config_for_reading(config, offset,
-					   OVERRIDE_FLAG_DIRECTION);
+	struct per_line_config *per_line;
+	size_t i;
 
-	return base->direction;
-}
+	for (i = 0; i < config->num_configs; i++) {
+		per_line = &config->line_configs[i];
 
-static void set_edge_detection(struct base_config *config, int edge)
-{
-	switch (edge) {
-	case GPIOD_LINE_EDGE_NONE:
-	case GPIOD_LINE_EDGE_RISING:
-	case GPIOD_LINE_EDGE_FALLING:
-	case GPIOD_LINE_EDGE_BOTH:
-		config->edge = edge;
-		break;
-	default:
-		config->edge = GPIOD_LINE_EDGE_NONE;
-		break;
+		if (per_line->offset == offset)
+			return gpiod_line_settings_copy(
+					per_line->node->settings);
 	}
-}
-
-GPIOD_API void
-gpiod_line_config_set_edge_detection_default(struct gpiod_line_config *config,
-					     int edge)
-{
-	set_edge_detection(&config->defaults, edge);
-}
-
-GPIOD_API void
-gpiod_line_config_set_edge_detection_override(struct gpiod_line_config *config,
-					      int edge, unsigned int offset)
-{
-	struct override_config *override;
-
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return;
-
-	set_edge_detection(&override->base, edge);
-	override->override_flags |= OVERRIDE_FLAG_EDGE;
-}
-
-GPIOD_API void
-gpiod_line_config_clear_edge_detection_override(
-			struct gpiod_line_config *config, unsigned int offset)
-{
-	clear_override(config, offset, OVERRIDE_FLAG_EDGE);
-}
 
-GPIOD_API bool
-gpiod_line_config_edge_detection_is_overridden(struct gpiod_line_config *config,
-					      unsigned int offset)
-{
-	return check_override(config, offset, OVERRIDE_FLAG_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_config_get_edge_detection_default(struct gpiod_line_config *config)
-{
-	return config->defaults.edge;
+	errno = ENOENT;
+	return NULL;
 }
 
 GPIOD_API int
-gpiod_line_config_get_edge_detection_offset(struct gpiod_line_config *config,
-					    unsigned int offset)
+gpiod_line_config_get_offsets(struct gpiod_line_config *config,
+			      size_t *num_offsets,
+			      unsigned int **offsets)
 {
-	struct base_config *base;
+	unsigned int *offs;
+	size_t i;
 
-	base = get_base_config_for_reading(config, offset, OVERRIDE_FLAG_EDGE);
+	*num_offsets = config->num_configs;
+	*offsets = NULL;
 
-	return base->edge;
-}
+	if (!config->num_configs)
+		return 0;
 
-static void set_bias(struct base_config *config, int bias)
-{
-	switch (bias) {
-	case GPIOD_LINE_BIAS_AS_IS:
-	case GPIOD_LINE_BIAS_DISABLED:
-	case GPIOD_LINE_BIAS_PULL_UP:
-	case GPIOD_LINE_BIAS_PULL_DOWN:
-		config->bias = bias;
-		break;
-	default:
-		config->bias = GPIOD_LINE_BIAS_AS_IS;
-		break;
-	}
-}
-
-GPIOD_API void
-gpiod_line_config_set_bias_default(struct gpiod_line_config *config, int bias)
-{
-	set_bias(&config->defaults, bias);
-}
+	offs = calloc(config->num_configs, sizeof(unsigned int));
+	if (!offs)
+		return -1;
 
-GPIOD_API void
-gpiod_line_config_set_bias_override(struct gpiod_line_config *config,
-				  int bias, unsigned int offset)
-{
-	struct override_config *override;
+	for (i = 0; i < config->num_configs; i++)
+		offs[i] = config->line_configs[i].offset;
 
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return;
+	*offsets = offs;
 
-	set_bias(&override->base, bias);
-	override->override_flags |= OVERRIDE_FLAG_BIAS;
+	return 0;
 }
 
-GPIOD_API void
-gpiod_line_config_clear_bias_override(struct gpiod_line_config *config,
-				      unsigned int offset)
+static void set_offsets(struct gpiod_line_config *config,
+			struct gpio_v2_line_request *uapi_cfg)
 {
-	clear_override(config, offset, OVERRIDE_FLAG_BIAS);
-}
+	size_t i;
 
-GPIOD_API bool
-gpiod_line_config_bias_is_overridden(struct gpiod_line_config *config,
-				     unsigned int offset)
-{
-	return check_override(config, offset, OVERRIDE_FLAG_BIAS);
-}
+	uapi_cfg->num_lines = config->num_configs;
 
-GPIOD_API int
-gpiod_line_config_get_bias_default(struct gpiod_line_config *config)
-{
-	return config->defaults.bias;
+	for (i = 0; i < config->num_configs; i++)
+		uapi_cfg->offsets[i] = config->line_configs[i].offset;
 }
 
-GPIOD_API int
-gpiod_line_config_get_bias_offset(struct gpiod_line_config *config,
-				  unsigned int offset)
+static bool has_at_least_one_output_direction(struct gpiod_line_config *config)
 {
-	struct base_config *base;
-
-	base = get_base_config_for_reading(config, offset, OVERRIDE_FLAG_BIAS);
-
-	return base->bias;
-}
+	size_t i;
 
-static void set_drive(struct base_config *config, int drive)
-{
-	switch (drive) {
-	case GPIOD_LINE_DRIVE_PUSH_PULL:
-	case GPIOD_LINE_DRIVE_OPEN_DRAIN:
-	case GPIOD_LINE_DRIVE_OPEN_SOURCE:
-		config->drive = drive;
-		break;
-	default:
-		config->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
-		break;
+	for (i = 0; i < config->num_configs; i++) {
+		if (gpiod_line_settings_get_direction(
+				config->line_configs[i].node->settings) ==
+		    GPIOD_LINE_DIRECTION_OUTPUT)
+			return true;
 	}
-}
-
-GPIOD_API void
-gpiod_line_config_set_drive_default(struct gpiod_line_config *config, int drive)
-{
-	set_drive(&config->defaults, drive);
-}
-
-GPIOD_API void
-gpiod_line_config_set_drive_override(struct gpiod_line_config *config,
-				     int drive, unsigned int offset)
-{
-	struct override_config *override;
-
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return;
-
-	set_drive(&override->base, drive);
-	override->override_flags |= OVERRIDE_FLAG_DRIVE;
-}
-
-GPIOD_API void
-gpiod_line_config_clear_drive_override(struct gpiod_line_config *config,
-				       unsigned int offset)
-{
-	clear_override(config, offset, OVERRIDE_FLAG_DRIVE);
-}
-
-GPIOD_API bool
-gpiod_line_config_drive_is_overridden(struct gpiod_line_config *config,
-				      unsigned int offset)
-{
-	return check_override(config, offset, OVERRIDE_FLAG_DRIVE);
-}
-
-GPIOD_API int
-gpiod_line_config_get_drive_default(struct gpiod_line_config *config)
-{
-	return config->defaults.drive;
-}
-
-GPIOD_API int
-gpiod_line_config_get_drive_offset(struct gpiod_line_config *config,
-				   unsigned int offset)
-{
-	struct base_config *base;
-
-	base = get_base_config_for_reading(config, offset, OVERRIDE_FLAG_DRIVE);
-
-	return base->drive;
-}
-
-GPIOD_API void
-gpiod_line_config_set_active_low_default(struct gpiod_line_config *config,
-					 bool active_low)
-{
-	config->defaults.active_low = active_low;
-}
-
-GPIOD_API void
-gpiod_line_config_set_active_low_override(struct gpiod_line_config *config,
-					  bool active_low,
-					  unsigned int offset)
-{
-	struct override_config *override;
-
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return;
-
-	override->base.active_low = active_low;
-	override->override_flags |= OVERRIDE_FLAG_ACTIVE_LOW;
-}
-
-GPIOD_API void
-gpiod_line_config_clear_active_low_override(struct gpiod_line_config *config,
-					    unsigned int offset)
-{
-	clear_override(config, offset, OVERRIDE_FLAG_ACTIVE_LOW);
-}
-
-GPIOD_API bool
-gpiod_line_config_active_low_is_overridden(struct gpiod_line_config *config,
-					   unsigned int offset)
-{
-	return check_override(config, offset, OVERRIDE_FLAG_ACTIVE_LOW);
-}
-
-GPIOD_API bool
-gpiod_line_config_get_active_low_default(struct gpiod_line_config *config)
-{
-	return config->defaults.active_low;
-}
-
-GPIOD_API bool
-gpiod_line_config_get_active_low_offset(struct gpiod_line_config *config,
-					unsigned int offset)
-{
-	struct base_config *base;
-
-	base = get_base_config_for_reading(config, offset,
-					   OVERRIDE_FLAG_ACTIVE_LOW);
-
-	return base->active_low;
-}
-
-GPIOD_API void
-gpiod_line_config_set_debounce_period_us_default(
-		struct gpiod_line_config *config, unsigned long period)
-{
-	config->defaults.debounce_period_us = period;
-}
-
-GPIOD_API void
-gpiod_line_config_set_debounce_period_us_override(
-					struct gpiod_line_config *config,
-					unsigned long period,
-					unsigned int offset)
-{
-	struct override_config *override;
-
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return;
 
-	override->base.debounce_period_us = period;
-	override->override_flags |= OVERRIDE_FLAG_DEBOUNCE_PERIOD;
-}
-
-GPIOD_API void gpiod_line_config_clear_debounce_period_us_override(
-					struct gpiod_line_config *config,
-					unsigned int offset)
-{
-	clear_override(config, offset, OVERRIDE_FLAG_DEBOUNCE_PERIOD);
-}
-
-GPIOD_API bool gpiod_line_config_debounce_period_us_is_overridden(
-					struct gpiod_line_config *config,
-					unsigned int offset)
-{
-	return check_override(config, offset, OVERRIDE_FLAG_DEBOUNCE_PERIOD);
+	return false;
 }
 
-GPIOD_API unsigned long
-gpiod_line_config_get_debounce_period_us_default(
-					struct gpiod_line_config *config)
+static void set_kernel_output_values(uint64_t *mask, uint64_t *vals,
+				     struct gpiod_line_config *config)
 {
-	return config->defaults.debounce_period_us;
-}
+	struct per_line_config *per_line;
+	int value;
+	size_t i;
 
-GPIOD_API unsigned long
-gpiod_line_config_get_debounce_period_us_offset(
-			struct gpiod_line_config *config, unsigned int offset)
-{
-	struct base_config *base;
+	gpiod_line_mask_zero(mask);
+	gpiod_line_mask_zero(vals);
 
-	base = get_base_config_for_reading(config, offset,
-					   OVERRIDE_FLAG_DEBOUNCE_PERIOD);
+	for (i = 0; i < config->num_configs; i++) {
+		per_line = &config->line_configs[i];
 
-	return base->debounce_period_us;
-}
+		if (gpiod_line_settings_get_direction(
+				per_line->node->settings) !=
+		    GPIOD_LINE_DIRECTION_OUTPUT)
+			continue;
 
-static void set_event_clock(struct base_config *config, int clock)
-{
-	switch (clock) {
-	case GPIOD_LINE_EVENT_CLOCK_MONOTONIC:
-	case GPIOD_LINE_EVENT_CLOCK_REALTIME:
-		config->clock = clock;
-		break;
-	default:
-		config->clock = GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
-		break;
+		gpiod_line_mask_set_bit(mask, i);
+		value = gpiod_line_settings_get_output_value(
+						per_line->node->settings);
+		gpiod_line_mask_assign_bit(vals, i,
+				value == GPIOD_LINE_VALUE_ACTIVE ? 1 : 0);
 	}
 }
 
-GPIOD_API void
-gpiod_line_config_set_event_clock_default(struct gpiod_line_config *config,
-					  int clock)
-{
-	set_event_clock(&config->defaults, clock);
-}
-
-GPIOD_API void
-gpiod_line_config_set_event_clock_override(struct gpiod_line_config *config,
-					   int clock, unsigned int offset)
-{
-	struct override_config *override;
-
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
-		return;
-
-	set_event_clock(&override->base, clock);
-	override->override_flags |= OVERRIDE_FLAG_CLOCK;
-}
-
-GPIOD_API void
-gpiod_line_config_clear_event_clock_override(struct gpiod_line_config *config,
-					     unsigned int offset)
+static void set_output_values(struct gpiod_line_config *config,
+			      struct gpio_v2_line_request *uapi_cfg,
+			      unsigned int *attr_idx)
 {
-	clear_override(config, offset, OVERRIDE_FLAG_CLOCK);
-}
-
-GPIOD_API bool
-gpiod_line_config_event_clock_is_overridden(struct gpiod_line_config *config,
-					    unsigned int offset)
-{
-	return check_override(config, offset, OVERRIDE_FLAG_CLOCK);
-}
-
-GPIOD_API int
-gpiod_line_config_get_event_clock_default(struct gpiod_line_config *config)
-{
-	return config->defaults.clock;
-}
-
-GPIOD_API int
-gpiod_line_config_get_event_clock_offset(struct gpiod_line_config *config,
-					 unsigned int offset)
-{
-	struct base_config *base;
-
-	base = get_base_config_for_reading(config, offset, OVERRIDE_FLAG_CLOCK);
-
-	return base->clock;
-}
-
-GPIOD_API void
-gpiod_line_config_set_output_value_default(struct gpiod_line_config *config,
-					   int value)
-{
-	config->defaults.value = value;
-}
-
-GPIOD_API void
-gpiod_line_config_set_output_value_override(struct gpiod_line_config *config,
-					    int value, unsigned int offset)
-{
-	struct override_config *override;
+	struct gpio_v2_line_config_attribute *attr;
+	uint64_t mask, values;
 
-	override = get_override_config_for_writing(config, offset);
-	if (!override)
+	if (!has_at_least_one_output_direction(config))
 		return;
 
-	override->base.value = !!value;
-	override->override_flags |= OVERRIDE_FLAG_OUTPUT_VALUE;
+	attr = &uapi_cfg->config.attrs[(*attr_idx)++];
+	attr->attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
+	set_kernel_output_values(&mask, &values, config);
+	attr->attr.values = values;
+	attr->mask = mask;
 }
 
-GPIOD_API void
-gpiod_line_config_set_output_values(struct gpiod_line_config *config,
-				    size_t num_values,
-				    const unsigned int *offsets,
-				    const int *values)
+static int set_debounce_periods(struct gpiod_line_config *config,
+				struct gpio_v2_line_config *uapi_cfg,
+				unsigned int *attr_idx)
 {
-	size_t i;
-
-	for (i = 0; i < num_values; i++)
-		gpiod_line_config_set_output_value_override(config,
-							    values[i],
-							    offsets[i]);
-}
-
-GPIOD_API void
-gpiod_line_config_clear_output_value_override(struct gpiod_line_config *config,
-					      unsigned int offset)
-{
-	clear_override(config, offset, OVERRIDE_FLAG_OUTPUT_VALUE);
-}
-
-GPIOD_API bool
-gpiod_line_config_output_value_is_overridden(struct gpiod_line_config *config,
-					     unsigned int offset)
-{
-	return check_override(config, offset, OVERRIDE_FLAG_OUTPUT_VALUE);
-}
-
-GPIOD_API int
-gpiod_line_config_get_output_value_default(struct gpiod_line_config *config)
-{
-	return config->defaults.value;
-}
-
-GPIOD_API int
-gpiod_line_config_get_output_value_offset(struct gpiod_line_config *config,
-					  unsigned int offset)
-{
-	struct override_config *override;
-
-	override = get_override_by_offset(config, offset);
-	if (override && (override->override_flags & OVERRIDE_FLAG_OUTPUT_VALUE))
-		return override->base.value;
-
-	return config->defaults.value;
-}
+	struct gpio_v2_line_config_attribute *attr;
+	unsigned long period_i, period_j;
+	uint64_t done, mask;
+	size_t i, j;
 
-static bool base_config_flags_are_equal(struct base_config *base,
-					struct override_config *override)
-{
-	if (((override->override_flags & OVERRIDE_FLAG_DIRECTION) &&
-	     base->direction != override->base.direction) ||
-	    ((override->override_flags & OVERRIDE_FLAG_EDGE) &&
-	     base->edge != override->base.edge) ||
-	    ((override->override_flags & OVERRIDE_FLAG_DRIVE) &&
-	     base->drive != override->base.drive) ||
-	    ((override->override_flags & OVERRIDE_FLAG_BIAS) &&
-	     base->bias != override->base.bias) ||
-	    ((override->override_flags & OVERRIDE_FLAG_ACTIVE_LOW) &&
-	     base->active_low != override->base.active_low) ||
-	    ((override->override_flags & OVERRIDE_FLAG_CLOCK) &&
-	     base->clock != override->base.clock))
-		return false;
+	gpiod_line_mask_zero(&done);
 
-	return true;
-}
-
-static bool base_debounce_period_is_equal(struct base_config *base,
-					  struct override_config *override)
-{
-	if ((override->override_flags & OVERRIDE_FLAG_DEBOUNCE_PERIOD) &&
-	    base->debounce_period_us != override->base.debounce_period_us)
-		return false;
+	for (i = 0; i < config->num_configs; i++) {
+		if (gpiod_line_mask_test_bit(&done, i))
+			continue;
 
-	return true;
-}
+		gpiod_line_mask_set_bit(&done, i);
+		gpiod_line_mask_zero(&mask);
 
-GPIOD_API size_t
-gpiod_line_config_get_num_overrides(struct gpiod_line_config *config)
-{
-	struct override_config *override;
-	size_t i, j, count = 0;
+		period_i = gpiod_line_settings_get_debounce_period_us(
+				config->line_configs[i].node->settings);
+		if (!period_i)
+			continue;
 
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-		override = &config->overrides[i];
+		if (*attr_idx == GPIO_V2_LINE_NUM_ATTRS_MAX) {
+			errno = E2BIG;
+			return -1;
+		}
 
-		if (override_used(override)) {
-			for (j = 0; j < NUM_OVERRIDE_FLAGS; j++) {
-				if (override->override_flags &
-				    override_flag_list[j])
-					count++;
+		attr = &uapi_cfg->attrs[(*attr_idx)++];
+		attr->attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+		attr->attr.debounce_period_us = period_i;
+		gpiod_line_mask_set_bit(&mask, i);
+
+		for (j = i; j < config->num_configs; j++) {
+			period_j = gpiod_line_settings_get_debounce_period_us(
+					config->line_configs[j].node->settings);
+			if (period_i == period_j) {
+				gpiod_line_mask_set_bit(&mask, j);
+				gpiod_line_mask_set_bit(&done, j);
 			}
 		}
-	}
 
-	return count;
-}
-
-static int override_flag_to_prop(int flag)
-{
-	switch (flag) {
-	case OVERRIDE_FLAG_DIRECTION:
-		return GPIOD_LINE_CONFIG_PROP_DIRECTION;
-	case OVERRIDE_FLAG_EDGE:
-		return GPIOD_LINE_CONFIG_PROP_EDGE_DETECTION;
-	case OVERRIDE_FLAG_BIAS:
-		return GPIOD_LINE_CONFIG_PROP_BIAS;
-	case OVERRIDE_FLAG_DRIVE:
-		return GPIOD_LINE_CONFIG_PROP_DRIVE;
-	case OVERRIDE_FLAG_ACTIVE_LOW:
-		return GPIOD_LINE_CONFIG_PROP_ACTIVE_LOW;
-	case OVERRIDE_FLAG_DEBOUNCE_PERIOD:
-		return GPIOD_LINE_CONFIG_PROP_DEBOUNCE_PERIOD_US;
-	case OVERRIDE_FLAG_CLOCK:
-		return GPIOD_LINE_CONFIG_PROP_EVENT_CLOCK;
-	case OVERRIDE_FLAG_OUTPUT_VALUE:
-		return GPIOD_LINE_CONFIG_PROP_OUTPUT_VALUE;
+		attr->mask = mask;
 	}
 
-	/* Can't happen. */
-	return -1;
-}
-
-GPIOD_API void
-gpiod_line_config_get_overrides(struct gpiod_line_config *config,
-				unsigned int *offsets, int *props)
-{
-	struct override_config *override;
-	size_t i, j, count = 0;
-
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-		override = &config->overrides[i];
-
-		if (override_used(override)) {
-			for (j = 0; j < NUM_OVERRIDE_FLAGS; j++) {
-				if (override->override_flags &
-				    override_flag_list[j]) {
-					offsets[count] = override->offset;
-					props[count] = override_flag_to_prop(
-							override_flag_list[j]);
-					count++;
-				}
-			}
-		}
-	}
+	return 0;
 }
 
-static uint64_t make_kernel_flags(const struct base_config *config)
+static uint64_t make_kernel_flags(struct gpiod_line_settings *settings)
 {
 	uint64_t flags = 0;
 
-	switch (config->direction) {
+	switch (gpiod_line_settings_get_direction(settings)) {
 	case GPIOD_LINE_DIRECTION_INPUT:
 		flags |= GPIO_V2_LINE_FLAG_INPUT;
 		break;
@@ -827,7 +294,7 @@ static uint64_t make_kernel_flags(const struct base_config *config)
 		break;
 	}
 
-	switch (config->edge) {
+	switch (gpiod_line_settings_get_edge_detection(settings)) {
 	case GPIOD_LINE_EDGE_FALLING:
 		flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
 			   GPIO_V2_LINE_FLAG_INPUT);
@@ -846,7 +313,7 @@ static uint64_t make_kernel_flags(const struct base_config *config)
 		break;
 	}
 
-	switch (config->drive) {
+	switch (gpiod_line_settings_get_drive(settings)) {
 	case GPIOD_LINE_DRIVE_OPEN_DRAIN:
 		flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
 		break;
@@ -855,7 +322,7 @@ static uint64_t make_kernel_flags(const struct base_config *config)
 		break;
 	}
 
-	switch (config->bias) {
+	switch (gpiod_line_settings_get_bias(settings)) {
 	case GPIOD_LINE_BIAS_DISABLED:
 		flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
 		break;
@@ -867,10 +334,10 @@ static uint64_t make_kernel_flags(const struct base_config *config)
 		break;
 	}
 
-	if (config->active_low)
+	if (gpiod_line_settings_get_active_low(settings))
 		flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
 
-	switch (config->clock) {
+	switch (gpiod_line_settings_get_event_clock(settings)) {
 	case GPIOD_LINE_EVENT_CLOCK_REALTIME:
 		flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
 		break;
@@ -879,341 +346,113 @@ static uint64_t make_kernel_flags(const struct base_config *config)
 	return flags;
 }
 
-static int find_bitmap_index(unsigned int needle, unsigned int num_lines,
-			     const unsigned int *haystack)
+static bool settings_equal(struct gpiod_line_settings *left,
+			 struct gpiod_line_settings *right)
 {
-	size_t i;
-
-	for (i = 0; i < num_lines; i++) {
-		if (needle == haystack[i])
-			return i;
-	}
-
-	return -1;
-}
-
-static void set_kernel_output_values(uint64_t *mask, uint64_t *vals,
-				     struct gpiod_line_config *config,
-				     unsigned int num_lines,
-				     const unsigned int *offsets)
-{
-	struct override_config *override;
-	size_t i;
-	int idx;
-
-	gpiod_line_mask_zero(mask);
-	gpiod_line_mask_zero(vals);
-
-	if (config->defaults.direction == GPIOD_LINE_DIRECTION_OUTPUT) {
-		/*
-		 * Default direction is output - assign the default output
-		 * value to all lines. Overrides that may set some lines to
-		 * input will be handled later and may re-assign the output
-		 * values.
-		 */
-		for (i = 0; i < num_lines; i++) {
-			gpiod_line_mask_set_bit(mask, i);
-			gpiod_line_mask_assign_bit(vals, i,
-						   config->defaults.value);
-		}
-	} else {
-		/*
-		 * Default output value is not output. Iterate over overrides
-		 * and set the default output value for those that override the
-		 * direction to output. Don't touch the ones which override
-		 * the output value.
-		 */
-		for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-			override = &config->overrides[i];
-
-			if (override->base.direction !=
-			    GPIOD_LINE_DIRECTION_OUTPUT ||
-			    !(override->override_flags &
-			      OVERRIDE_FLAG_DIRECTION) ||
-			    (override->override_flags &
-			     OVERRIDE_FLAG_OUTPUT_VALUE))
-				continue;
-
-			idx = find_bitmap_index(override->offset,
-						num_lines, offsets);
-			if (idx < 0)
-				continue;
-
-			gpiod_line_mask_set_bit(mask, idx);
-			gpiod_line_mask_assign_bit(vals, idx,
-						   !!config->defaults.value);
-		}
-	}
-
-	/*
-	 * Finally iterate over the overrides again and set the overridden
-	 * output values.
-	 */
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-		override = &config->overrides[i];
-
-		if (!(override->override_flags & OVERRIDE_FLAG_OUTPUT_VALUE))
-			continue;
-
-		if (config->defaults.direction != GPIOD_LINE_DIRECTION_OUTPUT &&
-		    (!(override->override_flags & OVERRIDE_FLAG_DIRECTION) ||
-		     override->base.direction != GPIOD_LINE_DIRECTION_OUTPUT))
-			continue;
-
-		idx = find_bitmap_index(override->offset, num_lines, offsets);
-		if (idx < 0)
-			continue;
-
-		gpiod_line_mask_set_bit(mask, idx);
-		gpiod_line_mask_assign_bit(vals, idx, !!override->base.value);
-	}
-}
-
-static bool override_config_flags_are_equal(struct override_config *a,
-					    struct override_config *b)
-{
-	if (((a->override_flags & ~OVERRIDE_FLAG_DEBOUNCE_PERIOD) ==
-	     (b->override_flags & ~OVERRIDE_FLAG_DEBOUNCE_PERIOD)) &&
-	    base_config_flags_are_equal(&a->base, b))
-		return true;
-
-	return false;
-}
-
-static void set_base_config_flags(struct gpio_v2_line_attribute *attr,
-				  struct override_config *override,
-				  struct gpiod_line_config *config)
-{
-	struct base_config base;
-
-	memcpy(&base, &config->defaults, sizeof(base));
-
-	if (override->override_flags & OVERRIDE_FLAG_DIRECTION)
-		base.direction = override->base.direction;
-	if (override->override_flags & OVERRIDE_FLAG_EDGE)
-		base.edge = override->base.edge;
-	if (override->override_flags & OVERRIDE_FLAG_BIAS)
-		base.bias = override->base.bias;
-	if (override->override_flags & OVERRIDE_FLAG_DRIVE)
-		base.drive = override->base.drive;
-	if (override->override_flags & OVERRIDE_FLAG_ACTIVE_LOW)
-		base.active_low = override->base.active_low;
-	if (override->override_flags & OVERRIDE_FLAG_CLOCK)
-		base.clock = override->base.clock;
-
-	attr->id = GPIO_V2_LINE_ATTR_ID_FLAGS;
-	attr->flags = make_kernel_flags(&base);
-}
-
-static bool override_config_debounce_period_is_equal(struct override_config *a,
-						     struct override_config *b)
-{
-	if (base_debounce_period_is_equal(&a->base, b) &&
-	    ((a->override_flags & OVERRIDE_FLAG_DEBOUNCE_PERIOD) ==
-	     (b->override_flags & OVERRIDE_FLAG_DEBOUNCE_PERIOD)))
-		return true;
-
-	return false;
-}
-
-static void
-set_base_config_debounce_period(struct gpio_v2_line_attribute *attr,
-				struct override_config *override,
-				struct gpiod_line_config *config GPIOD_UNUSED)
-{
-	attr->id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
-	attr->debounce_period_us = override->base.debounce_period_us;
-}
-
-static void set_kernel_attr_mask(uint64_t *out, const uint64_t *in,
-				 unsigned int num_lines,
-				 const unsigned int *offsets,
-				 struct gpiod_line_config *config)
-{
-	struct override_config *override;
-	size_t i, j;
-	int idx;
+	if (gpiod_line_settings_get_direction(left) !=
+	    gpiod_line_settings_get_direction(right))
+		return false;
 
-	gpiod_line_mask_zero(out);
+	if (gpiod_line_settings_get_edge_detection(left) !=
+	    gpiod_line_settings_get_edge_detection(right))
+		return false;
 
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-		override = &config->overrides[i];
+	if (gpiod_line_settings_get_bias(left) !=
+	    gpiod_line_settings_get_bias(right))
+		return false;
 
-		if (!override_used(override) ||
-		    !gpiod_line_mask_test_bit(in, i))
-			continue;
+	if (gpiod_line_settings_get_drive(left) !=
+	    gpiod_line_settings_get_drive(right))
+		return false;
 
-		for (j = 0, idx = -1; j < num_lines; j++) {
-			if (offsets[j] == override->offset) {
-				idx = j;
-				break;
-			}
-		}
+	if (gpiod_line_settings_get_active_low(left) !=
+	    gpiod_line_settings_get_active_low(right))
+		return false;
 
-		/*
-		 * Overridden offsets that are not in the list of offsets to
-		 * request (or already requested) are silently ignored.
-		 */
-		if (idx < 0)
-			continue;
+	if (gpiod_line_settings_get_event_clock(left) !=
+	    gpiod_line_settings_get_event_clock(right))
+		return false;
 
-		gpiod_line_mask_set_bit(out, idx);
-	}
+	return true;
 }
 
-static int process_overrides(struct gpiod_line_config *config,
-			     struct gpio_v2_line_config *uapi_cfg,
-			     unsigned int *attr_idx,
-			     unsigned int num_lines,
-			     const unsigned int *offsets,
-			     bool (*defaults_equal_func)(struct base_config *,
-						struct override_config *),
-			     bool (*override_equal_func)(
-						struct override_config *,
-						struct override_config *),
-			     void (*set_func)(struct gpio_v2_line_attribute *,
-					      struct override_config *,
-					      struct gpiod_line_config *))
+static int set_flags(struct gpiod_line_config *config,
+		     struct gpio_v2_line_config *uapi_cfg,
+		     unsigned int *attr_idx)
 {
+	struct gpiod_line_settings *settings_i, *settings_j;
 	struct gpio_v2_line_config_attribute *attr;
-	uint64_t processed = 0, marked = 0, mask;
-	struct override_config *current, *next;
+	bool globals_taken = false;
+	uint64_t done, mask;
 	size_t i, j;
 
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-		current = &config->overrides[i];
+	gpiod_line_mask_zero(&done);
 
-		if (!override_used(current) ||
-		    gpiod_line_mask_test_bit(&processed, i))
+	for (i = 0; i < config->num_configs; i++) {
+		if (gpiod_line_mask_test_bit(&done, i))
 			continue;
 
-		if (*attr_idx == GPIO_V2_LINE_NUM_ATTRS_MAX) {
-			errno = E2BIG;
-			return -1;
-		}
+		gpiod_line_mask_set_bit(&done, i);
 
-		gpiod_line_mask_set_bit(&processed, i);
+		settings_i = config->line_configs[i].node->settings;
 
-		if (defaults_equal_func(&config->defaults, current))
-			continue;
+		if (!globals_taken) {
+			globals_taken = true;
+			uapi_cfg->flags = make_kernel_flags(settings_i);
 
-		marked = 0;
-		gpiod_line_mask_set_bit(&marked, i);
+			for (j = i; j < config->num_configs; j++) {
+				settings_j =
+					config->line_configs[j].node->settings;
+				if (settings_equal(settings_i, settings_j))
+					gpiod_line_mask_set_bit(&done, j);
+			}
+		} else {
+			gpiod_line_mask_zero(&mask);
+			gpiod_line_mask_set_bit(&mask, i);
 
-		for (j = i + 1; j < NUM_OVERRIDES_MAX; j++) {
-			next = &config->overrides[j];
+			if (*attr_idx == GPIO_V2_LINE_NUM_ATTRS_MAX) {
+				errno = E2BIG;
+				return -1;
+			}
 
-			if (!override_used(next) ||
-			    gpiod_line_mask_test_bit(&processed, j))
-				continue;
+			attr = &uapi_cfg->attrs[(*attr_idx)++];
+			attr->attr.id = GPIO_V2_LINE_ATTR_ID_FLAGS;
+			attr->attr.flags = make_kernel_flags(settings_i);
 
-			if (override_equal_func(current, next)) {
-				gpiod_line_mask_set_bit(&marked, j);
-				gpiod_line_mask_set_bit(&processed, j);
+			for (j = i; j < config->num_configs; j++) {
+				settings_j =
+					config->line_configs[j].node->settings;
+				if (settings_equal(settings_i, settings_j)) {
+					gpiod_line_mask_set_bit(&done, j);
+					gpiod_line_mask_set_bit(&mask, j);
+				}
 			}
-		}
-
-		attr = &uapi_cfg->attrs[(*attr_idx)++];
 
-		set_kernel_attr_mask(&mask, &marked,
-				     num_lines, offsets, config);
-		attr->mask = mask;
-		set_func(&attr->attr, current, config);
+			attr->mask = mask;
+		}
 	}
 
 	return 0;
 }
 
-static bool has_at_least_one_output_direction(struct gpiod_line_config *config)
-{
-	struct override_config *override;
-	size_t i;
-
-	if (config->defaults.direction == GPIOD_LINE_DIRECTION_OUTPUT)
-		return true;
-
-	for (i = 0; i < NUM_OVERRIDES_MAX; i++) {
-		override = &config->overrides[i];
-
-		if (override->base.direction == GPIOD_LINE_DIRECTION_OUTPUT)
-			return true;
-	}
-
-	return false;
-}
-
 int gpiod_line_config_to_uapi(struct gpiod_line_config *config,
-			      struct gpio_v2_line_config *uapi_cfg,
-			      unsigned int num_lines,
-			      const unsigned int *offsets)
+			      struct gpio_v2_line_request *uapi_cfg)
 {
-	struct gpio_v2_line_config_attribute *attr;
 	unsigned int attr_idx = 0;
-	uint64_t mask, values;
 	int ret;
 
-	if (config->too_complex)
-		goto err_2big;
-
-	/*
-	 * First check if we have at least one line configured in output mode.
-	 * If so, let's take one attribute for the default values.
-	 */
-	if (has_at_least_one_output_direction(config)) {
-		attr = &uapi_cfg->attrs[attr_idx++];
-		attr->attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
-
-		set_kernel_output_values(&mask, &values, config,
-					 num_lines, offsets);
-
-		attr->attr.values = values;
-		attr->mask = mask;
-
-	}
-
-	/* If we have a default debounce period - use another attribute. */
-	if (config->defaults.debounce_period_us) {
-		attr = &uapi_cfg->attrs[attr_idx++];
-		attr->attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
-		attr->attr.debounce_period_us =
-				config->defaults.debounce_period_us;
-		gpiod_line_mask_fill(&mask);
-		attr->mask = mask;
-	}
+	set_offsets(config, uapi_cfg);
+	set_output_values(config, uapi_cfg, &attr_idx);
 
-	/*
-	 * The overrides are processed independently for regular flags and the
-	 * debounce period. We iterate over the configured line overrides. We
-	 * first check if the given set of options is equal to the global
-	 * defaults. If not, we mark it and iterate over the remaining
-	 * overrides looking for ones that have the same config as the one
-	 * currently processed. We mark them too and at the end we create a
-	 * single kernel attribute with the translated config and the mask
-	 * corresponding to all marked overrides. Those are now excluded from
-	 * further processing.
-	 */
-
-	ret = process_overrides(config, uapi_cfg, &attr_idx, num_lines, offsets,
-				base_config_flags_are_equal,
-				override_config_flags_are_equal,
-				set_base_config_flags);
+	ret = set_debounce_periods(config, &uapi_cfg->config, &attr_idx);
 	if (ret)
 		return -1;
 
-	ret = process_overrides(config, uapi_cfg, &attr_idx, num_lines, offsets,
-				base_debounce_period_is_equal,
-				override_config_debounce_period_is_equal,
-				set_base_config_debounce_period);
+	ret = set_flags(config, &uapi_cfg->config, &attr_idx);
 	if (ret)
 		return -1;
 
-	uapi_cfg->flags = make_kernel_flags(&config->defaults);
-	uapi_cfg->num_attrs = attr_idx;
+	uapi_cfg->config.num_attrs = attr_idx;
 
 	return 0;
-
-err_2big:
-	config->too_complex = true;
-	errno = E2BIG;
-	return -1;
 }
diff --git a/lib/line-request.c b/lib/line-request.c
index 04bd78d..ee452e7 100644
--- a/lib/line-request.c
+++ b/lib/line-request.c
@@ -172,21 +172,42 @@ GPIOD_API int gpiod_line_request_set_values(struct gpiod_line_request *request,
 						    request->offsets, values);
 }
 
+static bool offsets_equal(struct gpiod_line_request *request,
+			  struct gpio_v2_line_request *uapi_cfg)
+{
+	size_t i;
+
+	if (request->num_lines != uapi_cfg->num_lines)
+		return false;
+
+	for (i = 0; i < request->num_lines; i++) {
+		if (request->offsets[i] != uapi_cfg->offsets[i])
+			return false;
+	}
+
+	return true;
+}
+
 GPIOD_API int
 gpiod_line_request_reconfigure_lines(struct gpiod_line_request *request,
 				     struct gpiod_line_config *config)
 {
-	struct gpio_v2_line_config uapi_cfg;
+	struct gpio_v2_line_request uapi_cfg;
 	int ret;
 
 	memset(&uapi_cfg, 0, sizeof(uapi_cfg));
 
-	ret = gpiod_line_config_to_uapi(config, &uapi_cfg,
-					request->num_lines, request->offsets);
+	ret = gpiod_line_config_to_uapi(config, &uapi_cfg);
 	if (ret)
 		return ret;
 
-	ret = ioctl(request->fd, GPIO_V2_LINE_SET_CONFIG_IOCTL, &uapi_cfg);
+	if (!offsets_equal(request, &uapi_cfg)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	ret = ioctl(request->fd, GPIO_V2_LINE_SET_CONFIG_IOCTL,
+		    &uapi_cfg.config);
 	if (ret)
 		return ret;
 
diff --git a/lib/line-settings.c b/lib/line-settings.c
new file mode 100644
index 0000000..7125124
--- /dev/null
+++ b/lib/line-settings.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <gpiod.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "internal.h"
+
+struct gpiod_line_settings {
+	int direction;
+	int edge_detection;
+	int drive;
+	int bias;
+	bool active_low;
+	int event_clock;
+	long debounce_period_us;
+	int output_value;
+};
+
+GPIOD_API struct gpiod_line_settings *gpiod_line_settings_new(void)
+{
+	struct gpiod_line_settings *settings;
+
+	settings = malloc(sizeof(*settings));
+	if (!settings)
+		return NULL;
+
+	gpiod_line_settings_reset(settings);
+
+	return settings;
+}
+
+GPIOD_API void gpiod_line_settings_free(struct gpiod_line_settings *settings)
+{
+	free(settings);
+}
+
+GPIOD_API void gpiod_line_settings_reset(struct gpiod_line_settings *settings)
+{
+	settings->direction = GPIOD_LINE_DIRECTION_AS_IS;
+	settings->edge_detection = GPIOD_LINE_EDGE_NONE;
+	settings->bias = GPIOD_LINE_BIAS_AS_IS;
+	settings->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+	settings->active_low = false;
+	settings->debounce_period_us = 0;
+	settings->event_clock = GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
+	settings->output_value = GPIOD_LINE_VALUE_INACTIVE;
+}
+
+GPIOD_API struct gpiod_line_settings *
+gpiod_line_settings_copy(struct gpiod_line_settings *settings)
+{
+	struct gpiod_line_settings *copy;
+
+	copy = malloc(sizeof(*copy));
+	if (!copy)
+		return NULL;
+
+	memcpy(copy, settings, sizeof(*copy));
+
+	return copy;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_direction(struct gpiod_line_settings *settings,
+				  int direction)
+{
+	switch (direction) {
+	case GPIOD_LINE_DIRECTION_INPUT:
+	case GPIOD_LINE_DIRECTION_OUTPUT:
+	case GPIOD_LINE_DIRECTION_AS_IS:
+		settings->direction = direction;
+		break;
+	default:
+		settings->direction = GPIOD_LINE_DIRECTION_AS_IS;
+		errno = EINVAL;
+		return -1;
+	}
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_direction(struct gpiod_line_settings *settings)
+{
+	return settings->direction;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_edge_detection(struct gpiod_line_settings *settings,
+				       int edge)
+{
+	switch (edge) {
+	case GPIOD_LINE_EDGE_NONE:
+	case GPIOD_LINE_EDGE_RISING:
+	case GPIOD_LINE_EDGE_FALLING:
+	case GPIOD_LINE_EDGE_BOTH:
+		settings->edge_detection = edge;
+		break;
+	default:
+		settings->edge_detection = GPIOD_LINE_EDGE_NONE;
+		errno = EINVAL;
+		return -1;
+	}
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_edge_detection(struct gpiod_line_settings *settings)
+{
+	return settings->edge_detection;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_bias(struct gpiod_line_settings *settings, int bias)
+{
+	switch (bias) {
+	case GPIOD_LINE_BIAS_AS_IS:
+	case GPIOD_LINE_BIAS_DISABLED:
+	case GPIOD_LINE_BIAS_PULL_UP:
+	case GPIOD_LINE_BIAS_PULL_DOWN:
+		settings->bias = bias;
+		break;
+	default:
+		settings->bias = GPIOD_LINE_BIAS_AS_IS;
+		errno = EINVAL;
+		return -1;
+	}
+
+	return 0;
+}
+
+GPIOD_API int gpiod_line_settings_get_bias(struct gpiod_line_settings *settings)
+{
+	return settings->bias;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_drive(struct gpiod_line_settings *settings, int drive)
+{
+	switch (drive) {
+	case GPIOD_LINE_DRIVE_PUSH_PULL:
+	case GPIOD_LINE_DRIVE_OPEN_DRAIN:
+	case GPIOD_LINE_DRIVE_OPEN_SOURCE:
+		settings->drive = drive;
+		break;
+	default:
+		settings->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+		errno = EINVAL;
+		return -1;
+	}
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_drive(struct gpiod_line_settings *settings)
+{
+	return settings->drive;
+}
+
+GPIOD_API void
+gpiod_line_settings_set_active_low(struct gpiod_line_settings *settings,
+				   bool active_low)
+{
+	settings->active_low = active_low;
+}
+
+GPIOD_API bool
+gpiod_line_settings_get_active_low(struct gpiod_line_settings *settings)
+{
+	return settings->active_low;
+}
+
+GPIOD_API void
+gpiod_line_settings_set_debounce_period_us(struct gpiod_line_settings *settings,
+					   unsigned long period)
+{
+	settings->debounce_period_us = period;
+}
+
+GPIOD_API unsigned long
+gpiod_line_settings_get_debounce_period_us(struct gpiod_line_settings *settings)
+{
+	return settings->debounce_period_us;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_event_clock(struct gpiod_line_settings *settings,
+				    int event_clock)
+{
+	switch (event_clock) {
+	case GPIOD_LINE_EVENT_CLOCK_MONOTONIC:
+	case GPIOD_LINE_EVENT_CLOCK_REALTIME:
+		settings->event_clock = event_clock;
+		break;
+	default:
+		settings->event_clock = GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
+		errno = EINVAL;
+		return -1;
+	}
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_event_clock(struct gpiod_line_settings *settings)
+{
+	return settings->event_clock;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_output_value(struct gpiod_line_settings *settings,
+				     int value)
+{
+	switch (value) {
+	case GPIOD_LINE_VALUE_INACTIVE:
+	case GPIOD_LINE_VALUE_ACTIVE:
+		settings->output_value = value;
+		break;
+	default:
+		settings->output_value = GPIOD_LINE_VALUE_INACTIVE;
+		errno = EINVAL;
+		return -1;
+	}
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_output_value(struct gpiod_line_settings *settings)
+{
+	return settings->output_value;
+}
diff --git a/lib/request-config.c b/lib/request-config.c
index 3a84106..22106e8 100644
--- a/lib/request-config.c
+++ b/lib/request-config.c
@@ -10,8 +10,6 @@
 
 struct gpiod_request_config {
 	char consumer[GPIO_MAX_NAME_SIZE];
-	unsigned int offsets[GPIO_V2_LINES_MAX];
-	size_t num_offsets;
 	size_t event_buffer_size;
 };
 
@@ -50,34 +48,6 @@ gpiod_request_config_get_consumer(struct gpiod_request_config *config)
 	return config->consumer[0] == '\0' ? NULL : config->consumer;
 }
 
-GPIOD_API void
-gpiod_request_config_set_offsets(struct gpiod_request_config *config,
-				 size_t num_offsets,
-				 const unsigned int *offsets)
-{
-	size_t i;
-
-	config->num_offsets = num_offsets > GPIO_V2_LINES_MAX ?
-					GPIO_V2_LINES_MAX : num_offsets;
-
-	for (i = 0; i < config->num_offsets; i++)
-		config->offsets[i] = offsets[i];
-}
-
-GPIOD_API size_t
-gpiod_request_config_get_num_offsets(struct gpiod_request_config *config)
-{
-	return config->num_offsets;
-}
-
-GPIOD_API void
-gpiod_request_config_get_offsets(struct gpiod_request_config *config,
-				 unsigned int *offsets)
-{
-	memcpy(offsets, config->offsets,
-	       sizeof(*offsets) * config->num_offsets);
-}
-
 GPIOD_API void
 gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
 					   size_t event_buffer_size)
@@ -91,22 +61,9 @@ gpiod_request_config_get_event_buffer_size(struct gpiod_request_config *config)
 	return config->event_buffer_size;
 }
 
-int gpiod_request_config_to_uapi(struct gpiod_request_config *config,
-				 struct gpio_v2_line_request *uapi_req)
+void gpiod_request_config_to_uapi(struct gpiod_request_config *config,
+				  struct gpio_v2_line_request *uapi_req)
 {
-	size_t i;
-
-	if (config->num_offsets == 0) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	for (i = 0; i < config->num_offsets; i++)
-		uapi_req->offsets[i] = config->offsets[i];
-
-	uapi_req->num_lines = config->num_offsets;
 	strcpy(uapi_req->consumer, config->consumer);
 	uapi_req->event_buffer_size = config->event_buffer_size;
-
-	return 0;
 }
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f37dc03..392f03c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -29,5 +29,6 @@ gpiod_test_SOURCES =			\
 		tests-line-config.c	\
 		tests-line-info.c	\
 		tests-line-request.c	\
+		tests-line-settings.c	\
 		tests-misc.c		\
-		tests-request-config.c
+		tests-request-config.c
\ No newline at end of file
diff --git a/tests/gpiod-test-helpers.h b/tests/gpiod-test-helpers.h
index ca2c784..c3363bf 100644
--- a/tests/gpiod-test-helpers.h
+++ b/tests/gpiod-test-helpers.h
@@ -30,6 +30,10 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_info_event, gpiod_info_event_free);
 typedef struct gpiod_line_config struct_gpiod_line_config;
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_config, gpiod_line_config_free);
 
+typedef struct gpiod_line_settings struct_gpiod_line_settings;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_settings,
+			      gpiod_line_settings_free);
+
 typedef struct gpiod_request_config struct_gpiod_request_config;
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_request_config,
 			      gpiod_request_config_free);
@@ -84,6 +88,15 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_edge_event_buffer,
 		_info; \
 	})
 
+#define gpiod_test_create_line_settings_or_fail() \
+	({ \
+		struct gpiod_line_settings *_settings = \
+				gpiod_line_settings_new(); \
+		g_assert_nonnull(_settings); \
+		gpiod_test_return_if_failed(); \
+		_settings; \
+	})
+
 #define gpiod_test_create_line_config_or_fail() \
 	({ \
 		struct gpiod_line_config *_config = \
@@ -102,6 +115,17 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_edge_event_buffer,
 		_buffer; \
 	})
 
+#define gpiod_test_line_config_add_line_settings_or_fail(_line_cfg, _offsets, \
+						_num_offsets, _settings) \
+	do { \
+		gint ret = gpiod_line_config_add_line_settings((_line_cfg), \
+							       (_offsets),  \
+							       (_num_offsets), \
+							       (_settings)); \
+		g_assert_cmpint(ret, ==, 0); \
+		gpiod_test_return_if_failed(); \
+	} while (0)
+
 #define gpiod_test_create_request_config_or_fail() \
 	({ \
 		struct gpiod_request_config *_config = \
diff --git a/tests/tests-edge-event.c b/tests/tests-edge-event.c
index 987155f..66fe075 100644
--- a/tests/tests-edge-event.c
+++ b/tests/tests-edge-event.c
@@ -36,20 +36,20 @@ GPIOD_TEST_CASE(edge_event_wait_timeout)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	ret = gpiod_line_request_wait_edge_event(request, 1000000);
 	g_assert_cmpint(ret, ==, 0);
@@ -61,21 +61,22 @@ GPIOD_TEST_CASE(cannot_request_lines_in_output_mode_with_edge_detection)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_BOTH);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
 
-	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_chip_request_lines(chip, NULL, line_cfg);
 	g_assert_null(request);
 	gpiod_test_expect_errno(EINVAL);
 }
@@ -101,7 +102,7 @@ GPIOD_TEST_CASE(read_both_events)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
@@ -111,17 +112,18 @@ GPIOD_TEST_CASE(read_both_events)
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 	buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	thread = g_thread_new("request-release",
 			      falling_and_rising_edge_events, sim);
@@ -178,7 +180,7 @@ GPIOD_TEST_CASE(read_rising_edge_event)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
@@ -187,17 +189,19 @@ GPIOD_TEST_CASE(read_rising_edge_event)
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 	buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_RISING);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings,
+					       GPIOD_LINE_EDGE_RISING);
+
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	thread = g_thread_new("edge-generator",
 			      falling_and_rising_edge_events, sim);
@@ -236,7 +240,7 @@ GPIOD_TEST_CASE(read_falling_edge_event)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
@@ -245,17 +249,19 @@ GPIOD_TEST_CASE(read_falling_edge_event)
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 	buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_FALLING);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings,
+					       GPIOD_LINE_EDGE_FALLING);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	thread = g_thread_new("request-release",
 			      falling_and_rising_edge_events, sim);
@@ -294,7 +300,7 @@ GPIOD_TEST_CASE(read_rising_edge_event_polled)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
@@ -305,17 +311,19 @@ GPIOD_TEST_CASE(read_rising_edge_event_polled)
 	gint ret, fd;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 	buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_RISING);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings,
+					       GPIOD_LINE_EDGE_RISING);
+
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	thread = g_thread_new("edge-generator",
 			      falling_and_rising_edge_events, sim);
@@ -368,7 +376,7 @@ GPIOD_TEST_CASE(read_both_events_blocking)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
@@ -377,17 +385,18 @@ GPIOD_TEST_CASE(read_both_events_blocking)
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 	buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	thread = g_thread_new("request-release",
 			      falling_and_rising_edge_events, sim);
@@ -447,7 +456,7 @@ GPIOD_TEST_CASE(seqno)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
@@ -456,17 +465,18 @@ GPIOD_TEST_CASE(seqno)
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 	buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
 
-	gpiod_request_config_set_offsets(req_cfg, 2, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	thread = g_thread_new("request-release",
 			      rising_edge_events_on_two_offsets, sim);
@@ -517,7 +527,7 @@ GPIOD_TEST_CASE(event_copy)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
@@ -527,17 +537,18 @@ GPIOD_TEST_CASE(event_copy)
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 	buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
 
@@ -564,7 +575,7 @@ GPIOD_TEST_CASE(reading_more_events_than_the_queue_contains_doesnt_block)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
@@ -572,17 +583,18 @@ GPIOD_TEST_CASE(reading_more_events_than_the_queue_contains_doesnt_block)
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 	buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
 	g_usleep(500);
diff --git a/tests/tests-info-event.c b/tests/tests-info-event.c
index 3f18784..a960ba9 100644
--- a/tests/tests-info-event.c
+++ b/tests/tests-info-event.c
@@ -56,8 +56,9 @@ GPIOD_TEST_CASE(event_timeout)
 
 struct request_ctx {
 	struct gpiod_chip *chip;
-	struct gpiod_request_config *req_cfg;
 	struct gpiod_line_config *line_cfg;
+	struct gpiod_line_settings *settings;
+	guint offset;
 };
 
 static gpointer request_reconfigure_release_line(gpointer data)
@@ -68,16 +69,24 @@ static gpointer request_reconfigure_release_line(gpointer data)
 
 	g_usleep(1000);
 
-	request = gpiod_chip_request_lines(ctx->chip,
-					   ctx->req_cfg, ctx->line_cfg);
+	ret = gpiod_line_config_add_line_settings(ctx->line_cfg, &ctx->offset,
+						  1, ctx->settings);
+	g_assert_cmpint(ret, ==, 0);
+	if (g_test_failed())
+		return NULL;
+
+	request = gpiod_chip_request_lines(ctx->chip, NULL, ctx->line_cfg);
 	g_assert_nonnull(request);
 	if (g_test_failed())
 		return NULL;
 
 	g_usleep(1000);
 
-	gpiod_line_config_set_direction_default(ctx->line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_config_reset(ctx->line_cfg);
+	gpiod_line_settings_set_direction(ctx->settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_config_add_line_settings(ctx->line_cfg, &ctx->offset, 1,
+					    ctx->settings);
 
 	ret = gpiod_line_request_reconfigure_lines(request, ctx->line_cfg);
 	g_assert_cmpint(ret, ==, 0);
@@ -94,16 +103,14 @@ static gpointer request_reconfigure_release_line(gpointer data)
 
 GPIOD_TEST_CASE(request_reconfigure_release_events)
 {
-	static const guint offset = 3;
-
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
 	g_autoptr(struct_gpiod_line_info) info = NULL;
 	g_autoptr(struct_gpiod_info_event) request_event = NULL;
 	g_autoptr(struct_gpiod_info_event) reconfigure_event = NULL;
 	g_autoptr(struct_gpiod_info_event) release_event = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(GThread) thread = NULL;
 	struct gpiod_line_info *request_info, *reconfigure_info, *release_info;
 	guint64 request_ts, reconfigure_ts, release_ts;
@@ -111,10 +118,8 @@ GPIOD_TEST_CASE(request_reconfigure_release_events)
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
-
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
+	settings = gpiod_test_create_line_settings_or_fail();
 
 	info = gpiod_chip_watch_line_info(chip, 3);
 	g_assert_nonnull(info);
@@ -123,8 +128,9 @@ GPIOD_TEST_CASE(request_reconfigure_release_events)
 	g_assert_false(gpiod_line_info_is_used(info));
 
 	ctx.chip = chip;
-	ctx.req_cfg = req_cfg;
 	ctx.line_cfg = line_cfg;
+	ctx.settings = settings;
+	ctx.offset = 3;
 
 	thread = g_thread_new("request-release",
 			      request_reconfigure_release_line, &ctx);
@@ -193,15 +199,13 @@ GPIOD_TEST_CASE(request_reconfigure_release_events)
 }
 
 GPIOD_TEST_CASE(chip_fd_can_be_polled)
-{
-	static const guint offset = 3;
-
+{\
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
 	g_autoptr(struct_gpiod_line_info) info = NULL;
 	g_autoptr(struct_gpiod_info_event) event = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(GThread) thread = NULL;
 	struct gpiod_line_info *evinfo;
@@ -211,11 +215,9 @@ GPIOD_TEST_CASE(chip_fd_can_be_polled)
 	gint ret, fd;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-
 	info = gpiod_chip_watch_line_info(chip, 3);
 	g_assert_nonnull(info);
 	gpiod_test_return_if_failed();
@@ -223,8 +225,9 @@ GPIOD_TEST_CASE(chip_fd_can_be_polled)
 	g_assert_false(gpiod_line_info_is_used(info));
 
 	ctx.chip = chip;
-	ctx.req_cfg = req_cfg;
 	ctx.line_cfg = line_cfg;
+	ctx.settings = settings;
+	ctx.offset = 3;
 
 	thread = g_thread_new("request-release",
 			      request_reconfigure_release_line, &ctx);
@@ -266,22 +269,21 @@ GPIOD_TEST_CASE(unwatch_and_check_that_no_events_are_generated)
 	g_autoptr(struct_gpiod_chip) chip = NULL;
 	g_autoptr(struct_gpiod_line_info) info = NULL;
 	g_autoptr(struct_gpiod_info_event) event = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 NULL);
 
 	info = gpiod_chip_watch_line_info(chip, 3);
 	g_assert_nonnull(info);
 	gpiod_test_return_if_failed();
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	ret = gpiod_chip_wait_info_event(chip, 100000000);
 	g_assert_cmpint(ret, >, 0);
diff --git a/tests/tests-line-config.c b/tests/tests-line-config.c
index ec9bd0a..27cc228 100644
--- a/tests/tests-line-config.c
+++ b/tests/tests-line-config.c
@@ -4,7 +4,6 @@
 #include <errno.h>
 #include <glib.h>
 #include <gpiod.h>
-#include <stdint.h>
 
 #include "gpiod-test.h"
 #include "gpiod-test-helpers.h"
@@ -12,446 +11,206 @@
 
 #define GPIOD_TEST_GROUP "line-config"
 
-GPIOD_TEST_CASE(default_config)
-{
-	g_autoptr(struct_gpiod_line_config) config = NULL;
-
-	config = gpiod_test_create_line_config_or_fail();
-
-	g_assert_cmpint(gpiod_line_config_get_direction_default(config), ==,
-			GPIOD_LINE_DIRECTION_AS_IS);
-	g_assert_cmpint(gpiod_line_config_get_edge_detection_default(config),
-			==, GPIOD_LINE_EDGE_NONE);
-	g_assert_cmpint(gpiod_line_config_get_bias_default(config), ==,
-			GPIOD_LINE_BIAS_AS_IS);
-	g_assert_cmpint(gpiod_line_config_get_drive_default(config), ==,
-			GPIOD_LINE_DRIVE_PUSH_PULL);
-	g_assert_false(gpiod_line_config_get_active_low_default(config));
-	g_assert_cmpuint(
-		gpiod_line_config_get_debounce_period_us_default(config), ==,
-		0);
-	g_assert_cmpint(gpiod_line_config_get_event_clock_default(config), ==,
-			GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
-	g_assert_cmpint(gpiod_line_config_get_output_value_default(config), ==,
-			GPIOD_LINE_VALUE_INACTIVE);
-	g_assert_cmpuint(gpiod_line_config_get_num_overrides(config),
-			 ==, 0);
-}
-
-GPIOD_TEST_CASE(defaults_are_used_for_non_overridden_offsets)
-{
-	g_autoptr(struct_gpiod_line_config) config = NULL;
-
-	config = gpiod_test_create_line_config_or_fail();
-
-	g_assert_cmpint(gpiod_line_config_get_direction_offset(config, 4), ==,
-			GPIOD_LINE_DIRECTION_AS_IS);
-	g_assert_cmpint(gpiod_line_config_get_edge_detection_offset(config, 4),
-			==, GPIOD_LINE_EDGE_NONE);
-	g_assert_cmpint(gpiod_line_config_get_bias_offset(config, 4), ==,
-			GPIOD_LINE_BIAS_AS_IS);
-	g_assert_cmpint(gpiod_line_config_get_drive_offset(config, 4), ==,
-			GPIOD_LINE_DRIVE_PUSH_PULL);
-	g_assert_false(gpiod_line_config_get_active_low_offset(config, 4));
-	g_assert_cmpuint(
-		gpiod_line_config_get_debounce_period_us_offset(config, 4), ==,
-		0);
-	g_assert_cmpint(gpiod_line_config_get_event_clock_offset(config, 4),
-			==, GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
-	g_assert_cmpint(gpiod_line_config_get_output_value_offset(config, 4),
-			==, GPIOD_LINE_VALUE_INACTIVE);
-	g_assert_cmpuint(gpiod_line_config_get_num_overrides(config),
-			 ==, 0);
-}
-
-GPIOD_TEST_CASE(set_and_clear_direction_override)
+GPIOD_TEST_CASE(too_many_lines)
 {
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) config = NULL;
+	guint offsets[65], i;
+	gint ret;
 
+	settings = gpiod_test_create_line_settings_or_fail();
 	config = gpiod_test_create_line_config_or_fail();
 
-	g_assert_cmpint(gpiod_line_config_get_direction_default(config), ==,
-			GPIOD_LINE_DIRECTION_AS_IS);
-	gpiod_line_config_set_direction_override(config,
-						 GPIOD_LINE_DIRECTION_OUTPUT,
-						 3);
-
-	g_assert_cmpint(gpiod_line_config_get_direction_default(config), ==,
-			GPIOD_LINE_DIRECTION_AS_IS);
-	g_assert_cmpint(gpiod_line_config_get_direction_offset(config, 3), ==,
-			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_true(gpiod_line_config_direction_is_overridden(config, 3));
-	gpiod_line_config_clear_direction_override(config, 3);
-	g_assert_cmpint(gpiod_line_config_get_direction_offset(config, 3), ==,
-			GPIOD_LINE_DIRECTION_AS_IS);
-	g_assert_false(gpiod_line_config_direction_is_overridden(config, 3));
-}
-
-GPIOD_TEST_CASE(invalid_direction)
-{
-	g_autoptr(struct_gpiod_line_config) config = NULL;
-
-	config = gpiod_test_create_line_config_or_fail();
+	for (i = 0; i < 65; i++)
+		offsets[i] = i;
 
-	gpiod_line_config_set_direction_default(config, INT32_MAX);
-	g_assert_cmpint(gpiod_line_config_get_direction_default(config),
-			==, GPIOD_LINE_DIRECTION_AS_IS);
+	ret = gpiod_line_config_add_line_settings(config, offsets, 65,
+						  settings);
+	g_assert_cmpint(ret, <, 0);
+	g_assert_cmpint(errno, ==, E2BIG);
 }
 
-GPIOD_TEST_CASE(set_and_clear_edge_detection_override)
+GPIOD_TEST_CASE(get_line_settings)
 {
-	g_autoptr(struct_gpiod_line_config) config = NULL;
+	static const guint offsets[] = { 0, 1, 2, 3 };
 
-	config = gpiod_test_create_line_config_or_fail();
-
-	g_assert_cmpint(gpiod_line_config_get_edge_detection_default(config),
-			==, GPIOD_LINE_EDGE_NONE);
-	gpiod_line_config_set_edge_detection_override(config,
-						GPIOD_LINE_EDGE_FALLING, 3);
-
-	g_assert_cmpint(gpiod_line_config_get_edge_detection_default(config),
-			==, GPIOD_LINE_EDGE_NONE);
-	g_assert_cmpint(gpiod_line_config_get_edge_detection_offset(config, 3),
-			==, GPIOD_LINE_EDGE_FALLING);
-	g_assert_true(gpiod_line_config_edge_detection_is_overridden(config,
-								     3));
-	gpiod_line_config_clear_edge_detection_override(config, 3);
-	g_assert_cmpint(gpiod_line_config_get_edge_detection_offset(config, 3),
-			==, GPIOD_LINE_EDGE_NONE);
-	g_assert_false(gpiod_line_config_edge_detection_is_overridden(config,
-								      3));
-}
-
-GPIOD_TEST_CASE(invalid_edge)
-{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	g_autoptr(struct_gpiod_line_settings) retrieved = NULL;
 	g_autoptr(struct_gpiod_line_config) config = NULL;
 
+	settings = gpiod_test_create_line_settings_or_fail();
 	config = gpiod_test_create_line_config_or_fail();
 
-	gpiod_line_config_set_edge_detection_default(config, INT32_MAX);
-	g_assert_cmpint(gpiod_line_config_get_edge_detection_default(config),
-			==, GPIOD_LINE_EDGE_NONE);
-}
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+	gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+							 settings);
 
-GPIOD_TEST_CASE(set_and_clear_bias_override)
-{
-	g_autoptr(struct_gpiod_line_config) config = NULL;
+	retrieved = gpiod_line_config_get_line_settings(config, 2);
+	g_assert_nonnull(retrieved);
+	gpiod_test_return_if_failed();
 
-	config = gpiod_test_create_line_config_or_fail();
-
-	g_assert_cmpint(gpiod_line_config_get_bias_default(config),
-			==, GPIOD_LINE_BIAS_AS_IS);
-	gpiod_line_config_set_bias_override(config, GPIOD_LINE_BIAS_PULL_UP, 0);
-
-	g_assert_cmpint(gpiod_line_config_get_bias_default(config),
-			==, GPIOD_LINE_BIAS_AS_IS);
-	g_assert_cmpint(gpiod_line_config_get_bias_offset(config, 0),
-			==, GPIOD_LINE_BIAS_PULL_UP);
-	g_assert_true(gpiod_line_config_bias_is_overridden(config, 0));
-	gpiod_line_config_clear_bias_override(config, 0);
-	g_assert_cmpint(gpiod_line_config_get_bias_offset(config, 0),
-			==, GPIOD_LINE_BIAS_AS_IS);
-	g_assert_false(gpiod_line_config_bias_is_overridden(config, 0));
-}
-
-GPIOD_TEST_CASE(invalid_bias)
-{
-	g_autoptr(struct_gpiod_line_config) config = NULL;
-
-	config = gpiod_test_create_line_config_or_fail();
-
-	gpiod_line_config_set_bias_default(config, INT32_MAX);
-	g_assert_cmpint(gpiod_line_config_get_bias_default(config),
-			==, GPIOD_LINE_BIAS_AS_IS);
+	g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+			GPIOD_LINE_DIRECTION_INPUT);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_PULL_DOWN);
 }
 
-GPIOD_TEST_CASE(set_and_clear_drive_override)
+GPIOD_TEST_CASE(too_many_attrs)
 {
-	g_autoptr(struct_gpiod_line_config) config = NULL;
+	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+	g_autoptr(struct_gpiod_chip) chip = NULL;
+	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	g_autoptr(struct_gpiod_line_request) request = NULL;
+	guint offset;
 
-	config = gpiod_test_create_line_config_or_fail();
+	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+	settings = gpiod_test_create_line_settings_or_fail();
+	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	g_assert_cmpint(gpiod_line_config_get_drive_default(config),
-			==, GPIOD_LINE_DRIVE_PUSH_PULL);
-	gpiod_line_config_set_drive_override(config,
-					     GPIOD_LINE_DRIVE_OPEN_DRAIN, 3);
-
-	g_assert_cmpint(gpiod_line_config_get_drive_default(config),
-			==, GPIOD_LINE_DRIVE_PUSH_PULL);
-	g_assert_cmpint(gpiod_line_config_get_drive_offset(config, 3),
-			==, GPIOD_LINE_DRIVE_OPEN_DRAIN);
-	g_assert_true(gpiod_line_config_drive_is_overridden(config, 3));
-	gpiod_line_config_clear_drive_override(config, 3);
-	g_assert_cmpint(gpiod_line_config_get_drive_offset(config, 3),
-			==, GPIOD_LINE_DRIVE_PUSH_PULL);
-	g_assert_false(gpiod_line_config_drive_is_overridden(config, 3));
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+	offset = 0;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_debounce_period_us(settings, 1000);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+	offset = 1;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+	offset = 2;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+	offset = 3;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_DISABLED);
+	offset = 4;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_set_active_low(settings, true);
+	offset = 5;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_set_edge_detection(settings,
+					       GPIOD_LINE_EDGE_FALLING);
+	offset = 6;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_set_event_clock(settings,
+					     GPIOD_LINE_EVENT_CLOCK_REALTIME);
+	offset = 7;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_reset(settings);
+
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_DRAIN);
+	offset = 8;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_SOURCE);
+	offset = 9;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+	g_assert_null(request);
+	g_assert_cmpint(errno, ==, E2BIG);
 }
 
-GPIOD_TEST_CASE(invalid_drive)
+GPIOD_TEST_CASE(reset_config)
 {
-	g_autoptr(struct_gpiod_line_config) config = NULL;
-
-	config = gpiod_test_create_line_config_or_fail();
-
-	gpiod_line_config_set_drive_default(config, INT32_MAX);
-	g_assert_cmpint(gpiod_line_config_get_drive_default(config),
-			==, GPIOD_LINE_BIAS_AS_IS);
-}
+	static const guint offsets[] = { 0, 1, 2, 3 };
 
-GPIOD_TEST_CASE(set_and_clear_active_low_override)
-{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	g_autoptr(struct_gpiod_line_settings) retrieved0 = NULL;
+	g_autoptr(struct_gpiod_line_settings) retrieved1 = NULL;
 	g_autoptr(struct_gpiod_line_config) config = NULL;
 
+	settings = gpiod_test_create_line_settings_or_fail();
 	config = gpiod_test_create_line_config_or_fail();
 
-	g_assert_false(gpiod_line_config_get_active_low_default(config));
-	gpiod_line_config_set_active_low_override(config, true, 3);
-
-	g_assert_false(gpiod_line_config_get_active_low_default(config));
-	g_assert_true(gpiod_line_config_get_active_low_offset(config, 3));
-	g_assert_true(gpiod_line_config_active_low_is_overridden(config, 3));
-	gpiod_line_config_clear_active_low_override(config, 3);
-	g_assert_false(gpiod_line_config_get_active_low_offset(config, 3));
-	g_assert_false(gpiod_line_config_active_low_is_overridden(config, 3));
-}
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+	gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+							 settings);
 
-GPIOD_TEST_CASE(set_and_clear_debounce_period_override)
-{
-	g_autoptr(struct_gpiod_line_config) config = NULL;
-
-	config = gpiod_test_create_line_config_or_fail();
+	retrieved0 = gpiod_line_config_get_line_settings(config, 2);
+	g_assert_nonnull(retrieved0);
+	gpiod_test_return_if_failed();
 
-	g_assert_cmpuint(
-		gpiod_line_config_get_debounce_period_us_default(config),
-		==, 0);
-	gpiod_line_config_set_debounce_period_us_override(config, 5000, 3);
-
-	g_assert_cmpuint(
-		gpiod_line_config_get_debounce_period_us_default(config),
-		==, 0);
-	g_assert_cmpuint(
-		gpiod_line_config_get_debounce_period_us_offset(config, 3),
-		==, 5000);
-	g_assert_true(
-		gpiod_line_config_debounce_period_us_is_overridden(config, 3));
-	gpiod_line_config_clear_debounce_period_us_override(config, 3);
-	g_assert_cmpuint(
-		gpiod_line_config_get_debounce_period_us_offset(config, 3),
-		==, 0);
-	g_assert_false(
-		gpiod_line_config_debounce_period_us_is_overridden(config, 3));
-}
-
-GPIOD_TEST_CASE(set_and_clear_event_clock_override)
-{
-	g_autoptr(struct_gpiod_line_config) config = NULL;
+	g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+			GPIOD_LINE_DIRECTION_INPUT);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_PULL_DOWN);
 
-	config = gpiod_test_create_line_config_or_fail();
+	gpiod_line_config_reset(config);
 
-	g_assert_cmpint(gpiod_line_config_get_event_clock_default(config),
-			==, GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
-	gpiod_line_config_set_event_clock_override(config,
-					GPIOD_LINE_EVENT_CLOCK_REALTIME, 3);
-
-	g_assert_cmpint(gpiod_line_config_get_event_clock_default(config),
-			==, GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
-	g_assert_cmpint(gpiod_line_config_get_event_clock_offset(config, 3),
-			==, GPIOD_LINE_EVENT_CLOCK_REALTIME);
-	g_assert_true(gpiod_line_config_event_clock_is_overridden(config, 3));
-	gpiod_line_config_clear_event_clock_override(config, 3);
-	g_assert_cmpint(gpiod_line_config_get_event_clock_offset(config, 3),
-			==, GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
-	g_assert_false(gpiod_line_config_event_clock_is_overridden(config, 3));
+	retrieved1 = gpiod_line_config_get_line_settings(config, 2);
+	g_assert_null(retrieved1);
 }
 
-GPIOD_TEST_CASE(invalid_event_clock)
+GPIOD_TEST_CASE(get_offsets)
 {
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) config = NULL;
+	g_autofree guint *config_offs = NULL;
+	guint offsets[8];
+	size_t num_offsets;
+	gint ret;
 
+	settings = gpiod_test_create_line_settings_or_fail();
 	config = gpiod_test_create_line_config_or_fail();
 
-	gpiod_line_config_set_event_clock_default(config, INT32_MAX);
-	g_assert_cmpint(gpiod_line_config_get_event_clock_default(config),
-			==, GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
-}
-
-GPIOD_TEST_CASE(set_and_clear_output_value_override)
-{
-	g_autoptr(struct_gpiod_line_config) config = NULL;
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+	offsets[0] = 2;
+	offsets[1] = 4;
+	gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 2,
+							 settings);
 
-	config = gpiod_test_create_line_config_or_fail();
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+	offsets[0] = 6;
+	offsets[1] = 7;
+	gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 2,
+							 settings);
 
-	g_assert_cmpint(gpiod_line_config_get_output_value_default(config),
-			==, GPIOD_LINE_VALUE_INACTIVE);
-	gpiod_line_config_set_output_value_override(config,
-						GPIOD_LINE_VALUE_ACTIVE, 3);
-
-	g_assert_cmpint(gpiod_line_config_get_output_value_default(config),
-			==, GPIOD_LINE_VALUE_INACTIVE);
-	g_assert_cmpint(gpiod_line_config_get_output_value_offset(config, 3),
-			==, GPIOD_LINE_VALUE_ACTIVE);
-	g_assert_true(gpiod_line_config_output_value_is_overridden(config, 3));
-	gpiod_line_config_clear_output_value_override(config, 3);
-	g_assert_cmpint(gpiod_line_config_get_output_value_offset(config, 3),
-			==, 0);
-	g_assert_false(gpiod_line_config_output_value_is_overridden(config, 3));
+	ret = gpiod_line_config_get_offsets(config, &num_offsets, &config_offs);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpuint(num_offsets, ==, 4);
+	g_assert_cmpuint(config_offs[0], ==, 2);
+	g_assert_cmpuint(config_offs[1], ==, 4);
+	g_assert_cmpuint(config_offs[2], ==, 6);
+	g_assert_cmpuint(config_offs[3], ==, 7);
 }
 
-GPIOD_TEST_CASE(set_multiple_output_values)
+GPIOD_TEST_CASE(get_0_offsets)
 {
-	static const guint offsets[] = { 3, 4, 5, 6 };
-	static const gint values[] = { GPIOD_LINE_VALUE_ACTIVE,
-				       GPIOD_LINE_VALUE_INACTIVE,
-				       GPIOD_LINE_VALUE_ACTIVE,
-				       GPIOD_LINE_VALUE_INACTIVE };
-
 	g_autoptr(struct_gpiod_line_config) config = NULL;
-	guint overridden_offsets[4], i;
-	gint overriden_props[4];
+	g_autofree guint *offsets = NULL;
+	size_t num_offsets;
+	gint ret;
 
 	config = gpiod_test_create_line_config_or_fail();
 
-	gpiod_line_config_set_output_values(config, 4, offsets, values);
-
-	g_assert_cmpint(gpiod_line_config_get_output_value_default(config),
-			==, 0);
-
-	for (i = 0; i < 4; i++)
-		g_assert_cmpint(
-			gpiod_line_config_get_output_value_offset(config,
-								  offsets[i]),
-			==, values[i]);
-
-	g_assert_cmpuint(gpiod_line_config_get_num_overrides(config),
-			==, 4);
-	gpiod_line_config_get_overrides(config,
-					overridden_offsets, overriden_props);
-
-	for (i = 0; i < 4; i++) {
-		g_assert_cmpuint(overridden_offsets[i], ==, offsets[i]);
-		g_assert_cmpint(overriden_props[i], ==,
-				GPIOD_LINE_CONFIG_PROP_OUTPUT_VALUE);
-	}
-}
-
-GPIOD_TEST_CASE(config_too_complex)
-{
-	static guint offsets[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
-
-	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 16, NULL);
-	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
-	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
-	g_autoptr(struct_gpiod_line_request) request = NULL;
-
-	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	line_cfg = gpiod_test_create_line_config_or_fail();
-	req_cfg = gpiod_test_create_request_config_or_fail();
-
-	/*
-	 * We need to make the line_config structure exceed the kernel's
-	 * maximum of 10 attributes.
-	 */
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_OUTPUT, 0);
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_INPUT, 1);
-	gpiod_line_config_set_edge_detection_override(line_cfg,
-						      GPIOD_LINE_EDGE_BOTH, 2);
-	gpiod_line_config_set_debounce_period_us_override(line_cfg, 1000, 2);
-	gpiod_line_config_set_active_low_override(line_cfg, true, 3);
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_OUTPUT, 4);
-	gpiod_line_config_set_drive_override(line_cfg,
-					     GPIOD_LINE_DRIVE_OPEN_DRAIN, 4);
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_OUTPUT, 8);
-	gpiod_line_config_set_drive_override(line_cfg,
-					     GPIOD_LINE_DRIVE_OPEN_SOURCE, 8);
-	gpiod_line_config_set_direction_override(line_cfg,
-						 GPIOD_LINE_DIRECTION_INPUT, 5);
-	gpiod_line_config_set_bias_override(line_cfg,
-					    GPIOD_LINE_BIAS_PULL_DOWN, 5);
-	gpiod_line_config_set_event_clock_override(line_cfg,
-					GPIOD_LINE_EVENT_CLOCK_REALTIME, 6);
-	gpiod_line_config_set_output_value_override(line_cfg,
-						GPIOD_LINE_VALUE_ACTIVE, 7);
-
-	gpiod_request_config_set_offsets(req_cfg, 12, offsets);
-
-	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
-	g_assert_null(request);
-	gpiod_test_expect_errno(E2BIG);
-}
-
-/*
- * This triggers the E2BIG error by exhausting the number of overrides in
- * the line_config structure instead of making the kernel representation too
- * complex.
- */
-GPIOD_TEST_CASE(define_too_many_overrides)
-{
-	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 128, NULL);
-	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
-	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
-	g_autoptr(struct_gpiod_line_request) request = NULL;
-	guint offsets[65], i;
-
-	for (i = 0; i < 65; i++)
-		offsets[i] = i;
-
-	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	line_cfg = gpiod_test_create_line_config_or_fail();
-	req_cfg = gpiod_test_create_request_config_or_fail();
-
-	for (i = 0; i < 65; i++)
-		gpiod_line_config_set_direction_override(line_cfg,
-				GPIOD_LINE_DIRECTION_OUTPUT, offsets[i]);
-
-	gpiod_request_config_set_offsets(req_cfg, 64, offsets);
-
-	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
-	g_assert_null(request);
-	gpiod_test_expect_errno(E2BIG);
-}
-
-GPIOD_TEST_CASE(ignore_overrides_for_offsets_not_in_request_config)
-{
-	static guint offsets[] = { 2, 3, 4, 6, 7 };
-
-	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
-	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
-	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
-	g_autoptr(struct_gpiod_line_request) request = NULL;
-	g_autoptr(struct_gpiod_line_info) info3 = NULL;
-	g_autoptr(struct_gpiod_line_info) info4 = NULL;
-
-	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	line_cfg = gpiod_test_create_line_config_or_fail();
-	req_cfg = gpiod_test_create_request_config_or_fail();
-
-	gpiod_request_config_set_offsets(req_cfg, 5, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_OUTPUT, 4);
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_OUTPUT, 5);
-
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-	info3 = gpiod_test_get_line_info_or_fail(chip, 3);
-	info4 = gpiod_test_get_line_info_or_fail(chip, 4);
-
-	g_assert_cmpint(gpiod_line_info_get_direction(info3), ==,
-			GPIOD_LINE_DIRECTION_INPUT);
-	g_assert_cmpint(gpiod_line_info_get_direction(info4), ==,
-			GPIOD_LINE_DIRECTION_OUTPUT);
-
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_OUTPUT, 0);
-
-	gpiod_test_reconfigure_lines_or_fail(request, line_cfg);
-	/* Nothing to check, value successfully ignored. */
+	ret = gpiod_line_config_get_offsets(config, &num_offsets, &offsets);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpuint(num_offsets, ==, 0);
+	g_assert_null(offsets);
 }
diff --git a/tests/tests-line-info.c b/tests/tests-line-info.c
index 757baf6..ffc4586 100644
--- a/tests/tests-line-info.c
+++ b/tests/tests-line-info.c
@@ -112,61 +112,115 @@ GPIOD_TEST_CASE(copy_line_info)
 			 gpiod_line_info_get_offset(copy));
 }
 
+GPIOD_TEST_CASE(direction_settings)
+{
+	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+	g_autoptr(struct_gpiod_chip) chip = NULL;
+	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	g_autoptr(struct_gpiod_line_request) request = NULL;
+	g_autoptr(struct_gpiod_line_info) info0 = NULL;
+	g_autoptr(struct_gpiod_line_info) info1 = NULL;
+	g_autoptr(struct_gpiod_line_info) info2 = NULL;
+	guint offset;
+
+	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+	settings = gpiod_test_create_line_settings_or_fail();
+	line_cfg = gpiod_test_create_line_config_or_fail();
+
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	offset = 0;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	offset = 1;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_AS_IS);
+	offset = 2;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+	info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+	info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+	info2 = gpiod_test_get_line_info_or_fail(chip, 2);
+
+	g_assert_cmpint(gpiod_line_info_get_direction(info0), ==,
+			GPIOD_LINE_DIRECTION_OUTPUT);
+	g_assert_cmpint(gpiod_line_info_get_direction(info1), ==,
+			GPIOD_LINE_DIRECTION_INPUT);
+	g_assert_cmpint(gpiod_line_info_get_direction(info2), ==,
+			GPIOD_LINE_DIRECTION_INPUT);
+}
+
 GPIOD_TEST_CASE(active_high)
 {
 	static const guint offset = 5;
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(struct_gpiod_line_info) info = NULL;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_active_low_default(line_cfg, true);
+	gpiod_line_settings_set_active_low(settings, true);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset,
+							 1, settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-	info = gpiod_chip_get_line_info(chip, 5);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+	info = gpiod_test_get_line_info_or_fail(chip, 5);
 
 	g_assert_true(gpiod_line_info_is_active_low(info));
 }
 
 GPIOD_TEST_CASE(edge_detection_settings)
 {
-	static const guint offsets[] = { 0, 1, 2, 3 };
-
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_info) info0 = NULL;
 	g_autoptr(struct_gpiod_line_info) info1 = NULL;
 	g_autoptr(struct_gpiod_line_info) info2 = NULL;
 	g_autoptr(struct_gpiod_line_info) info3 = NULL;
+	guint offset;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
-
-	gpiod_request_config_set_offsets(req_cfg, 4, offsets);
-	gpiod_line_config_set_edge_detection_override(line_cfg,
-						GPIOD_LINE_EDGE_RISING, 1);
-	gpiod_line_config_set_edge_detection_override(line_cfg,
-						GPIOD_LINE_EDGE_FALLING, 2);
-	gpiod_line_config_set_edge_detection_override(line_cfg,
-						GPIOD_LINE_EDGE_BOTH, 3);
-
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-	info0 = gpiod_chip_get_line_info(chip, 0);
-	info1 = gpiod_chip_get_line_info(chip, 1);
-	info2 = gpiod_chip_get_line_info(chip, 2);
-	info3 = gpiod_chip_get_line_info(chip, 3);
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_NONE);
+	offset = 0;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_RISING);
+	offset = 1;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_edge_detection(settings,
+					       GPIOD_LINE_EDGE_FALLING);
+	offset = 2;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+	offset = 3;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+	info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+	info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+	info2 = gpiod_test_get_line_info_or_fail(chip, 2);
+	info3 = gpiod_test_get_line_info_or_fail(chip, 3);
 
 	g_assert_cmpint(gpiod_line_info_get_edge_detection(info0), ==,
 			GPIOD_LINE_EDGE_NONE);
@@ -180,37 +234,43 @@ GPIOD_TEST_CASE(edge_detection_settings)
 
 GPIOD_TEST_CASE(bias_settings)
 {
-	static const guint offsets[] = { 0, 1, 2, 3 };
-
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(struct_gpiod_line_info) info0 = NULL;
 	g_autoptr(struct_gpiod_line_info) info1 = NULL;
 	g_autoptr(struct_gpiod_line_info) info2 = NULL;
 	g_autoptr(struct_gpiod_line_info) info3 = NULL;
+	guint offset;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 4, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_bias_override(line_cfg,
-					    GPIOD_LINE_BIAS_DISABLED, 1);
-	gpiod_line_config_set_bias_override(line_cfg,
-					    GPIOD_LINE_BIAS_PULL_DOWN, 2);
-	gpiod_line_config_set_bias_override(line_cfg,
-					    GPIOD_LINE_BIAS_PULL_UP, 3);
-
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-	info0 = gpiod_chip_get_line_info(chip, 0);
-	info1 = gpiod_chip_get_line_info(chip, 1);
-	info2 = gpiod_chip_get_line_info(chip, 2);
-	info3 = gpiod_chip_get_line_info(chip, 3);
+	gpiod_line_settings_set_direction(settings,GPIOD_LINE_DIRECTION_OUTPUT);
+	offset = 0;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_DISABLED);
+	offset = 1;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+	offset = 2;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+	offset = 3;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+	info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+	info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+	info2 = gpiod_test_get_line_info_or_fail(chip, 2);
+	info3 = gpiod_test_get_line_info_or_fail(chip, 3);
 
 	g_assert_cmpint(gpiod_line_info_get_bias(info0), ==,
 			GPIOD_LINE_BIAS_UNKNOWN);
@@ -224,33 +284,38 @@ GPIOD_TEST_CASE(bias_settings)
 
 GPIOD_TEST_CASE(drive_settings)
 {
-	static const guint offsets[] = { 0, 1, 2 };
-
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(struct_gpiod_line_info) info0 = NULL;
 	g_autoptr(struct_gpiod_line_info) info1 = NULL;
 	g_autoptr(struct_gpiod_line_info) info2 = NULL;
+	guint offset;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
-
-	gpiod_request_config_set_offsets(req_cfg, 3, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_drive_override(line_cfg,
-					     GPIOD_LINE_DRIVE_OPEN_DRAIN, 1);
-	gpiod_line_config_set_drive_override(line_cfg,
-					     GPIOD_LINE_DRIVE_OPEN_SOURCE, 2);
-
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-	info0 = gpiod_chip_get_line_info(chip, 0);
-	info1 = gpiod_chip_get_line_info(chip, 1);
-	info2 = gpiod_chip_get_line_info(chip, 2);
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	offset = 0;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_DRAIN);
+	offset = 1;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_SOURCE);
+	offset = 2;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+	info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+	info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+	info2 = gpiod_test_get_line_info_or_fail(chip, 2);
 
 	g_assert_cmpint(gpiod_line_info_get_drive(info0), ==,
 			GPIOD_LINE_DRIVE_PUSH_PULL);
@@ -266,22 +331,23 @@ GPIOD_TEST_CASE(debounce_period)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(struct_gpiod_line_info) info = NULL;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_edge_detection_default(line_cfg,
-						     GPIOD_LINE_EDGE_BOTH);
-	gpiod_line_config_set_debounce_period_us_default(line_cfg, 1000);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_debounce_period_us(settings, 1000);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-	info = gpiod_chip_get_line_info(chip, 5);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+	info = gpiod_test_get_line_info_or_fail(chip, 5);
 
 	g_assert_cmpuint(gpiod_line_info_get_debounce_period_us(info),
 			 ==, 1000);
@@ -289,27 +355,32 @@ GPIOD_TEST_CASE(debounce_period)
 
 GPIOD_TEST_CASE(event_clock)
 {
-	static const guint offsets[] = { 0, 1 };
-
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(struct_gpiod_line_info) info0 = NULL;
 	g_autoptr(struct_gpiod_line_info) info1 = NULL;
+	guint offset;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	offset = 0;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_event_clock(settings,
+					    GPIOD_LINE_EVENT_CLOCK_REALTIME);
+	offset = 1;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	gpiod_request_config_set_offsets(req_cfg, 2, offsets);
-	gpiod_line_config_set_event_clock_override(line_cfg,
-					GPIOD_LINE_EVENT_CLOCK_REALTIME, 1);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-	info0 = gpiod_chip_get_line_info(chip, 0);
-	info1 = gpiod_chip_get_line_info(chip, 1);
+	info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+	info1 = gpiod_test_get_line_info_or_fail(chip, 1);
 
 	g_assert_cmpint(gpiod_line_info_get_event_clock(info0), ==,
 			GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
diff --git a/tests/tests-line-request.c b/tests/tests-line-request.c
index 6949043..a905941 100644
--- a/tests/tests-line-request.c
+++ b/tests/tests-line-request.c
@@ -14,17 +14,14 @@ GPIOD_TEST_CASE(request_fails_with_no_offsets)
 {
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	g_assert_cmpuint(gpiod_request_config_get_num_offsets(req_cfg), ==, 0);
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
 
-	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	request = gpiod_chip_request_lines(chip, NULL, line_cfg);
 	g_assert_null(request);
 	gpiod_test_expect_errno(EINVAL);
 }
@@ -35,19 +32,25 @@ GPIOD_TEST_CASE(request_fails_with_duplicate_offsets)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
+	size_t num_requested_offsets;
+	guint requested_offsets[3];
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 4, offsets);
-
-	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
-	g_assert_null(request);
-	gpiod_test_expect_errno(EBUSY);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+							 NULL);
+
+	request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+	g_assert_nonnull(request);
+	num_requested_offsets = gpiod_line_request_get_num_lines(request);
+	g_assert_cmpuint(num_requested_offsets, ==, 3);
+	gpiod_line_request_get_offsets(request, requested_offsets);
+	g_assert_cmpuint(requested_offsets[0], ==, 0);
+	g_assert_cmpuint(requested_offsets[1], ==, 2);
+	g_assert_cmpuint(requested_offsets[2], ==, 3);
 }
 
 GPIOD_TEST_CASE(request_fails_with_offset_out_of_bounds)
@@ -56,17 +59,16 @@ GPIOD_TEST_CASE(request_fails_with_offset_out_of_bounds)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 2, offsets);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+							 NULL);
 
-	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	request = gpiod_chip_request_lines(chip, NULL, line_cfg);
 	g_assert_null(request);
 	gpiod_test_expect_errno(EINVAL);
 }
@@ -87,13 +89,16 @@ GPIOD_TEST_CASE(set_consumer)
 	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
 	gpiod_request_config_set_consumer(req_cfg, consumer);
 
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 NULL);
+
 	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
 
 	info = gpiod_test_get_line_info_or_fail(chip, offset);
 
+	g_assert_true(gpiod_line_info_is_used(info));
 	g_assert_cmpstr(gpiod_line_info_get_consumer(info), ==, consumer);
 }
 
@@ -103,18 +108,17 @@ GPIOD_TEST_CASE(empty_consumer)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	g_autoptr(struct_gpiod_line_info) info = NULL;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 NULL);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	info = gpiod_test_get_line_info_or_fail(chip, offset);
 
@@ -131,24 +135,25 @@ GPIOD_TEST_CASE(default_output_value)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	guint i;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 4, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_output_value_default(line_cfg,
-						   GPIOD_LINE_VALUE_ACTIVE);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+							 settings);
 
 	g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	for (i = 0; i < 4; i++)
 		g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[i]),
@@ -158,39 +163,6 @@ GPIOD_TEST_CASE(default_output_value)
 			GPIOD_LINE_VALUE_INACTIVE);
 }
 
-GPIOD_TEST_CASE(default_and_overridden_output_value)
-{
-	static const guint offsets[] = { 0, 1, 2, 3 };
-
-	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
-	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
-	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
-	g_autoptr(struct_gpiod_line_request) request = NULL;
-
-	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
-	line_cfg = gpiod_test_create_line_config_or_fail();
-
-	gpiod_request_config_set_offsets(req_cfg, 4, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_output_value_default(line_cfg, 1);
-	gpiod_line_config_set_output_value_override(line_cfg,
-						GPIOD_LINE_VALUE_INACTIVE, 2);
-
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-
-	g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[0]),
-			==, GPIOD_LINE_VALUE_ACTIVE);
-	g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[1]),
-			==, GPIOD_LINE_VALUE_ACTIVE);
-	g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[2]),
-			==, GPIOD_LINE_VALUE_INACTIVE);
-	g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[3]),
-			==, GPIOD_LINE_VALUE_ACTIVE);
-}
-
 GPIOD_TEST_CASE(read_all_values)
 {
 	static const guint offsets[] = { 0, 2, 4, 5, 7 };
@@ -198,21 +170,21 @@ GPIOD_TEST_CASE(read_all_values)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	gint ret, values[5];
 	guint i;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 5, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	for (i = 0; i < 5; i++)
 		g_gpiosim_chip_set_pull(sim, offsets[i],
@@ -233,21 +205,21 @@ GPIOD_TEST_CASE(request_multiple_values_but_read_one)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	gint ret;
 	guint i;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 5, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	for (i = 0; i < 5; i++)
 		g_gpiosim_chip_set_pull(sim, offsets[i],
@@ -268,21 +240,22 @@ GPIOD_TEST_CASE(set_all_values)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	gint ret;
 	guint i;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 5, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	ret = gpiod_line_request_set_values(request, values);
 	g_assert_cmpint(ret, ==, 0);
@@ -299,21 +272,22 @@ GPIOD_TEST_CASE(set_line_after_requesting)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 4, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_output_value_default(line_cfg,
-						   GPIOD_LINE_VALUE_INACTIVE);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_output_value(settings,
+					     GPIOD_LINE_VALUE_INACTIVE);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	gpiod_line_request_set_value(request, 1, GPIOD_LINE_VALUE_ACTIVE);
 
@@ -329,22 +303,23 @@ GPIOD_TEST_CASE(request_survives_parent_chip)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, &offset);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_output_value_default(line_cfg,
-						   GPIOD_LINE_VALUE_ACTIVE);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_output_value(settings,
+					     GPIOD_LINE_VALUE_ACTIVE);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	g_assert_cmpint(gpiod_line_request_get_value(request, offset), ==,
 			GPIOD_LINE_VALUE_ACTIVE);
@@ -363,64 +338,23 @@ GPIOD_TEST_CASE(request_survives_parent_chip)
 	gpiod_test_return_if_failed();
 }
 
-GPIOD_TEST_CASE(request_with_overridden_direction)
-{
-	static const guint offsets[] = { 0, 1, 2, 3 };
-
-	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
-	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
-	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
-	g_autoptr(struct_gpiod_line_request) request = NULL;
-	g_autoptr(struct_gpiod_line_info) info0 = NULL;
-	g_autoptr(struct_gpiod_line_info) info1 = NULL;
-	g_autoptr(struct_gpiod_line_info) info2 = NULL;
-	g_autoptr(struct_gpiod_line_info) info3 = NULL;
-
-	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
-	line_cfg = gpiod_test_create_line_config_or_fail();
-
-	gpiod_request_config_set_offsets(req_cfg, 4, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_direction_override(line_cfg,
-						 GPIOD_LINE_DIRECTION_INPUT, 3);
-
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
-	info0 = gpiod_test_get_line_info_or_fail(chip, 0);
-	info1 = gpiod_test_get_line_info_or_fail(chip, 1);
-	info2 = gpiod_test_get_line_info_or_fail(chip, 2);
-	info3 = gpiod_test_get_line_info_or_fail(chip, 3);
-
-	g_assert_cmpint(gpiod_line_info_get_direction(info0), ==,
-			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_info_get_direction(info1), ==,
-			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_info_get_direction(info2), ==,
-			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_info_get_direction(info3), ==,
-			GPIOD_LINE_DIRECTION_INPUT);
-}
-
-GPIOD_TEST_CASE(num_lines)
+GPIOD_TEST_CASE(num_lines_and_offsets)
 {
 	static const guint offsets[] = { 0, 1, 2, 3, 7, 8, 11, 14 };
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 16, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 	guint read_back[8], i;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 8, offsets);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 8,
+							 NULL);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	g_assert_cmpuint(gpiod_line_request_get_num_lines(request), ==, 8);
 	gpiod_test_return_if_failed();
@@ -431,29 +365,31 @@ GPIOD_TEST_CASE(num_lines)
 
 GPIOD_TEST_CASE(active_low_read_value)
 {
-	static const guint offsets[] = { 2, 3 };
-
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
+	guint offset;
 	gint value;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 2, offsets);
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_INPUT, 2);
-	gpiod_line_config_set_direction_override(line_cfg,
-					GPIOD_LINE_DIRECTION_OUTPUT, 3);
-	gpiod_line_config_set_active_low_default(line_cfg, true);
-	gpiod_line_config_set_output_value_default(line_cfg,
-						   GPIOD_LINE_VALUE_ACTIVE);
+	gpiod_line_settings_set_active_low(settings, true);
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	offset = 2;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+	offset = 3;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
 	value = gpiod_line_request_get_value(request, 2);
@@ -464,49 +400,64 @@ GPIOD_TEST_CASE(active_low_read_value)
 
 GPIOD_TEST_CASE(reconfigure_lines)
 {
-	static const guint offsets[] = { 0, 1, 2, 3 };
+	//static const guint offsets[] = { 0, 1, 2, 3 };
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
-	gint values[4], ret;
-	guint i;
+	guint offsets[2];
+	gint ret;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 4, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-
-	values[0] = 1;
-	values[1] = 0;
-	values[2] = 1;
-	values[3] = 0;
-	gpiod_line_config_set_output_values(line_cfg, 4, offsets, values);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+
+	gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+	offsets[0] = 0;
+	offsets[1] = 2;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+							 settings);
+	gpiod_line_settings_set_output_value(settings,
+					     GPIOD_LINE_VALUE_INACTIVE);
+	offsets[0] = 1;
+	offsets[1] = 3;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==, 1);
+	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==, 0);
+	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==, 1);
+	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, 0);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	gpiod_line_config_reset(line_cfg);
 
-	for (i = 0; i < 4; i++)
-		g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[i]),
-				==, values[i]);
-
-	values[0] = 0;
-	values[1] = 1;
-	values[2] = 0;
-	values[3] = 1;
-	gpiod_line_config_set_output_values(line_cfg, 4, offsets, values);
+	gpiod_line_settings_set_output_value(settings,
+					     GPIOD_LINE_VALUE_INACTIVE);
+	offsets[0] = 0;
+	offsets[1] = 2;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+							 settings);
+	gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+	offsets[0] = 1;
+	offsets[1] = 3;
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+							 settings);
 
 	ret = gpiod_line_request_reconfigure_lines(request, line_cfg);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	for (i = 0; i < 4; i++)
-		g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[i]),
-				==, values[i]);
+	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==, 0);
+	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==, 1);
+	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==, 0);
+	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, 1);
 }
 
 GPIOD_TEST_CASE(request_lines_with_unordered_offsets)
@@ -515,32 +466,34 @@ GPIOD_TEST_CASE(request_lines_with_unordered_offsets)
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
-	guint cfg_offsets[4];
+	guint set_offsets[4];
 	gint values[4];
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 6, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_output_value_default(line_cfg, 1);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
+	gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 6,
+							 settings);
+
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	values[0] = 0;
 	values[1] = 1;
 	values[2] = 0;
 	values[3] = 0;
-	cfg_offsets[0] = 7;
-	cfg_offsets[1] = 1;
-	cfg_offsets[2] = 6;
-	cfg_offsets[3] = 0;
-	gpiod_line_config_set_output_values(line_cfg, 4, cfg_offsets, values);
-
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	set_offsets[0] = 7;
+	set_offsets[1] = 1;
+	set_offsets[2] = 6;
+	set_offsets[3] = 0;
+	gpiod_line_request_set_values_subset(request, 4, set_offsets, values);
 
 	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==,
 			GPIOD_LINE_VALUE_INACTIVE);
@@ -558,24 +511,24 @@ GPIOD_TEST_CASE(request_lines_with_unordered_offsets)
 
 GPIOD_TEST_CASE(request_with_bias_set_to_pull_up)
 {
-	static const guint offsets[] = { 3 };
+	static const guint offset = 3;
 
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
 	g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
 	g_autoptr(struct_gpiod_line_request) request = NULL;
 
 	chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
-	req_cfg = gpiod_test_create_request_config_or_fail();
+	settings = gpiod_test_create_line_settings_or_fail();
 	line_cfg = gpiod_test_create_line_config_or_fail();
 
-	gpiod_request_config_set_offsets(req_cfg, 1, offsets);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_INPUT);
-	gpiod_line_config_set_bias_default(line_cfg, GPIOD_LINE_BIAS_PULL_UP);
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+	gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+							 settings);
 
-	request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+	request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
 
 	g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
 			GPIOD_LINE_VALUE_ACTIVE);
diff --git a/tests/tests-line-settings.c b/tests/tests-line-settings.c
new file mode 100644
index 0000000..d074063
--- /dev/null
+++ b/tests/tests-line-settings.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+
+#define GPIOD_TEST_GROUP "line-settings"
+
+GPIOD_TEST_CASE(default_config)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+			GPIOD_LINE_DIRECTION_AS_IS);
+	g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+			GPIOD_LINE_EDGE_NONE);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_AS_IS);
+	g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+			GPIOD_LINE_DRIVE_PUSH_PULL);
+	g_assert_false(gpiod_line_settings_get_active_low(settings));
+	g_assert_cmpuint(gpiod_line_settings_get_debounce_period_us(settings),
+			==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+			GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+	g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+			GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(set_direction)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	gint ret;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	ret = gpiod_line_settings_set_direction(settings,
+						GPIOD_LINE_DIRECTION_INPUT);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+			GPIOD_LINE_DIRECTION_INPUT);
+
+	ret = gpiod_line_settings_set_direction(settings,
+						GPIOD_LINE_DIRECTION_AS_IS);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+			GPIOD_LINE_DIRECTION_AS_IS);
+
+	ret = gpiod_line_settings_set_direction(settings,
+						GPIOD_LINE_DIRECTION_OUTPUT);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+			GPIOD_LINE_DIRECTION_OUTPUT);
+
+	ret = gpiod_line_settings_set_direction(settings, 999);
+	g_assert_cmpint(ret, <, 0);
+	g_assert_cmpint(errno, ==, EINVAL);
+	g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+			GPIOD_LINE_DIRECTION_AS_IS);
+}
+
+GPIOD_TEST_CASE(set_edge_detection)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	gint ret;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	ret = gpiod_line_settings_set_edge_detection(settings,
+						     GPIOD_LINE_EDGE_BOTH);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+			GPIOD_LINE_EDGE_BOTH);
+
+	ret = gpiod_line_settings_set_edge_detection(settings,
+						     GPIOD_LINE_EDGE_NONE);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+			GPIOD_LINE_EDGE_NONE);
+
+	ret = gpiod_line_settings_set_edge_detection(settings,
+						     GPIOD_LINE_EDGE_FALLING);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+			GPIOD_LINE_EDGE_FALLING);
+
+	ret = gpiod_line_settings_set_edge_detection(settings,
+						     GPIOD_LINE_EDGE_RISING);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+			GPIOD_LINE_EDGE_RISING);
+
+	ret = gpiod_line_settings_set_edge_detection(settings, 999);
+	g_assert_cmpint(ret, <, 0);
+	g_assert_cmpint(errno, ==, EINVAL);
+	g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+			GPIOD_LINE_EDGE_NONE);
+}
+
+GPIOD_TEST_CASE(set_bias)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	gint ret;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	ret = gpiod_line_settings_set_bias(settings,
+					   GPIOD_LINE_BIAS_DISABLED);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_DISABLED);
+
+	ret = gpiod_line_settings_set_bias(settings,
+					   GPIOD_LINE_BIAS_AS_IS);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_AS_IS);
+
+	ret = gpiod_line_settings_set_bias(settings,
+					   GPIOD_LINE_BIAS_PULL_DOWN);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_PULL_DOWN);
+
+	ret = gpiod_line_settings_set_bias(settings,
+					   GPIOD_LINE_BIAS_PULL_UP);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_PULL_UP);
+
+	ret = gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_UNKNOWN);
+	g_assert_cmpint(ret, <, 0);
+	g_assert_cmpint(errno, ==, EINVAL);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_AS_IS);
+
+	ret = gpiod_line_settings_set_bias(settings, 999);
+	g_assert_cmpint(ret, <, 0);
+	g_assert_cmpint(errno, ==, EINVAL);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_AS_IS);
+}
+
+GPIOD_TEST_CASE(set_drive)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	gint ret;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	ret = gpiod_line_settings_set_drive(settings,
+					    GPIOD_LINE_DRIVE_OPEN_DRAIN);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+			GPIOD_LINE_DRIVE_OPEN_DRAIN);
+
+	ret = gpiod_line_settings_set_drive(settings,
+					    GPIOD_LINE_DRIVE_PUSH_PULL);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+			GPIOD_LINE_DRIVE_PUSH_PULL);
+
+	ret = gpiod_line_settings_set_drive(settings,
+					    GPIOD_LINE_DRIVE_OPEN_SOURCE);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+			GPIOD_LINE_DRIVE_OPEN_SOURCE);
+
+	ret = gpiod_line_settings_set_drive(settings, 999);
+	g_assert_cmpint(ret, <, 0);
+	g_assert_cmpint(errno, ==, EINVAL);
+	g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+			GPIOD_LINE_DRIVE_PUSH_PULL);
+}
+
+GPIOD_TEST_CASE(set_active_low)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	gpiod_line_settings_set_active_low(settings, true);
+	g_assert_true(gpiod_line_settings_get_active_low(settings));
+
+	gpiod_line_settings_set_active_low(settings, false);
+	g_assert_false(gpiod_line_settings_get_active_low(settings));
+}
+
+GPIOD_TEST_CASE(set_debounce_period)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	gpiod_line_settings_set_debounce_period_us(settings, 4000);
+	g_assert_cmpint(gpiod_line_settings_get_debounce_period_us(settings),
+			==, 4000);
+}
+
+GPIOD_TEST_CASE(set_event_clock)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	gint ret;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	ret = gpiod_line_settings_set_event_clock(settings,
+					GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+			GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+
+	ret = gpiod_line_settings_set_event_clock(settings,
+					GPIOD_LINE_EVENT_CLOCK_REALTIME);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+			GPIOD_LINE_EVENT_CLOCK_REALTIME);
+
+	ret = gpiod_line_settings_set_event_clock(settings, 999);
+	g_assert_cmpint(ret, <, 0);
+	g_assert_cmpint(errno, ==, EINVAL);
+	g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+			GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+}
+
+GPIOD_TEST_CASE(set_output_value)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	gint ret;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	ret = gpiod_line_settings_set_output_value(settings,
+						   GPIOD_LINE_VALUE_ACTIVE);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+			GPIOD_LINE_VALUE_ACTIVE);
+
+	ret = gpiod_line_settings_set_output_value(settings,
+						   GPIOD_LINE_VALUE_INACTIVE);
+	g_assert_cmpint(ret, ==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+			GPIOD_LINE_VALUE_INACTIVE);
+
+	ret = gpiod_line_settings_set_output_value(settings, 999);
+	g_assert_cmpint(ret, <, 0);
+	g_assert_cmpint(errno, ==, EINVAL);
+	g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+			GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(copy_line_settings)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+	g_autoptr(struct_gpiod_line_settings) copy = NULL;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_debounce_period_us(settings, 2000);
+	gpiod_line_settings_set_event_clock(settings,
+					    GPIOD_LINE_EVENT_CLOCK_REALTIME);
+
+	copy = gpiod_line_settings_copy(settings);
+	g_assert_nonnull(copy);
+	gpiod_test_return_if_failed();
+	g_assert_false(settings == copy);
+	g_assert_cmpint(gpiod_line_settings_get_direction(copy), ==,
+			GPIOD_LINE_DIRECTION_INPUT);
+	g_assert_cmpint(gpiod_line_settings_get_edge_detection(copy), ==,
+			GPIOD_LINE_EDGE_BOTH);
+	g_assert_cmpint(gpiod_line_settings_get_debounce_period_us(copy), ==,
+			2000);
+	g_assert_cmpint(gpiod_line_settings_get_event_clock(copy), ==,
+			GPIOD_LINE_EVENT_CLOCK_REALTIME);
+}
+
+GPIOD_TEST_CASE(reset_settings)
+{
+	g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+	settings = gpiod_test_create_line_settings_or_fail();
+
+	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+	gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+	gpiod_line_settings_set_debounce_period_us(settings, 2000);
+	gpiod_line_settings_set_event_clock(settings,
+					    GPIOD_LINE_EVENT_CLOCK_REALTIME);
+
+	gpiod_line_settings_reset(settings);
+
+	g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+			GPIOD_LINE_DIRECTION_AS_IS);
+	g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+			GPIOD_LINE_EDGE_NONE);
+	g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+			GPIOD_LINE_BIAS_AS_IS);
+	g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+			GPIOD_LINE_DRIVE_PUSH_PULL);
+	g_assert_false(gpiod_line_settings_get_active_low(settings));
+	g_assert_cmpuint(gpiod_line_settings_get_debounce_period_us(settings),
+			==, 0);
+	g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+			GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+	g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+			GPIOD_LINE_VALUE_INACTIVE);
+}
diff --git a/tests/tests-request-config.c b/tests/tests-request-config.c
index becb414..f26c05a 100644
--- a/tests/tests-request-config.c
+++ b/tests/tests-request-config.c
@@ -16,12 +16,11 @@ GPIOD_TEST_CASE(default_config)
 	config = gpiod_test_create_request_config_or_fail();
 
 	g_assert_null(gpiod_request_config_get_consumer(config));
-	g_assert_cmpuint(gpiod_request_config_get_num_offsets(config), ==, 0);
 	g_assert_cmpuint(gpiod_request_config_get_event_buffer_size(config),
 			 ==, 0);
 }
 
-GPIOD_TEST_CASE(consumer)
+GPIOD_TEST_CASE(set_consumer)
 {
 	g_autoptr(struct_gpiod_request_config) config = NULL;
 
@@ -32,53 +31,7 @@ GPIOD_TEST_CASE(consumer)
 			==, "foobar");
 }
 
-GPIOD_TEST_CASE(offsets)
-{
-	static const guint offsets[] = { 0, 3, 4, 7 };
-
-	g_autoptr(struct_gpiod_request_config) config = NULL;
-	guint read_back[4], i;
-
-	config = gpiod_test_create_request_config_or_fail();
-
-	gpiod_request_config_set_offsets(config, 4, offsets);
-	g_assert_cmpuint(gpiod_request_config_get_num_offsets(config), ==, 4);
-	memset(read_back, 0, sizeof(read_back));
-	gpiod_request_config_get_offsets(config, read_back);
-	for (i = 0; i < 4; i++)
-		g_assert_cmpuint(read_back[i], ==, offsets[i]);
-}
-
-GPIOD_TEST_CASE(max_offsets)
-{
-	static const guint offsets_good[] = {
-		 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
-		16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
-		32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
-		48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63
-	};
-
-	static const guint offsets_bad[] = {
-		 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
-		16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
-		32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
-		48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
-		64
-	};
-
-	g_autoptr(struct_gpiod_request_config) config = NULL;
-
-	config = gpiod_test_create_request_config_or_fail();
-
-	gpiod_request_config_set_offsets(config, 64, offsets_good);
-	g_assert_cmpuint(gpiod_request_config_get_num_offsets(config), ==, 64);
-
-	gpiod_request_config_set_offsets(config, 65, offsets_bad);
-	/* Should get truncated. */
-	g_assert_cmpuint(gpiod_request_config_get_num_offsets(config), ==, 64);
-}
-
-GPIOD_TEST_CASE(event_buffer_size)
+GPIOD_TEST_CASE(set_event_buffer_size)
 {
 	g_autoptr(struct_gpiod_request_config) config = NULL;
 
diff --git a/tools/gpio-tools-test.bats b/tools/gpio-tools-test.bats
index 69ad786..a259eae 100755
--- a/tools/gpio-tools-test.bats
+++ b/tools/gpio-tools-test.bats
@@ -449,7 +449,7 @@ teardown() {
 	run_tool gpioget "$(gpiosim_chip_name sim0)" 0 0
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request lines"
+	output_regex_match ".*offsets must be unique"
 }
 
 @test "gpioget: invalid bias" {
@@ -737,7 +737,7 @@ teardown() {
 	run_tool gpioset "$(gpiosim_chip_name sim0)" 0=1 0=1
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request lines"
+	output_regex_match ".*offsets must be unique"
 }
 
 #
@@ -957,7 +957,7 @@ teardown() {
 	run_tool gpiomon "$(gpiosim_chip_name sim0)" 0 0
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request lines"
+	output_regex_match ".*offsets must be unique"
 }
 
 @test "gpiomon: no arguments" {
diff --git a/tools/gpioget.c b/tools/gpioget.c
index ae80271..b68212d 100644
--- a/tools/gpioget.c
+++ b/tools/gpioget.c
@@ -43,6 +43,7 @@ int main(int argc, char **argv)
 {
 	int direction = GPIOD_LINE_DIRECTION_INPUT;
 	int optc, opti, bias = 0, ret, *values;
+	struct gpiod_line_settings *settings;
 	struct gpiod_request_config *req_cfg;
 	struct gpiod_line_request *request;
 	struct gpiod_line_config *line_cfg;
@@ -103,28 +104,39 @@ int main(int argc, char **argv)
 			die("invalid GPIO offset: %s", argv[i + 1]);
 	}
 
+	if (has_duplicate_offsets(num_lines, offsets))
+		die("offsets must be unique");
+
 	chip = chip_open_lookup(device);
 	if (!chip)
 		die_perror("unable to open %s", device);
 
-	line_cfg = gpiod_line_config_new();
-	if (!line_cfg)
-		die_perror("unable to allocate the line config structure");
+	settings = gpiod_line_settings_new();
+	if (!settings)
+		die_perror("unable to allocate line settings");
 
-	gpiod_line_config_set_direction_default(line_cfg, direction);
+	gpiod_line_settings_set_direction(settings, direction);
 
 	if (bias)
-		gpiod_line_config_set_bias_default(line_cfg, bias);
+		gpiod_line_settings_set_bias(settings, bias);
 
 	if (active_low)
-		gpiod_line_config_set_active_low_default(line_cfg, true);
+		gpiod_line_settings_set_active_low(settings, active_low);
 
 	req_cfg = gpiod_request_config_new();
 	if (!req_cfg)
 		die_perror("unable to allocate the request config structure");
 
 	gpiod_request_config_set_consumer(req_cfg, "gpioget");
-	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
+
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
+
+	ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
+						  num_lines, settings);
+	if (ret)
+		die_perror("unable to add line settings");
 
 	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
 	if (!request)
@@ -144,6 +156,7 @@ int main(int argc, char **argv)
 	gpiod_line_request_release(request);
 	gpiod_request_config_free(req_cfg);
 	gpiod_line_config_free(line_cfg);
+	gpiod_line_settings_free(settings);
 	gpiod_chip_close(chip);
 	free(offsets);
 	free(values);
diff --git a/tools/gpiomon.c b/tools/gpiomon.c
index f6a0dba..dff12ea 100644
--- a/tools/gpiomon.c
+++ b/tools/gpiomon.c
@@ -159,6 +159,7 @@ int main(int argc, char **argv)
 	struct gpiod_edge_event_buffer *event_buffer;
 	int optc, opti, ret, i, edge, bias = 0;
 	uint64_t timeout = 10 * 1000000000LLU;
+	struct gpiod_line_settings *settings;
 	struct gpiod_request_config *req_cfg;
 	struct gpiod_line_request *request;
 	struct gpiod_line_config *line_cfg;
@@ -250,26 +251,37 @@ int main(int argc, char **argv)
 		num_lines++;
 	}
 
+	if (has_duplicate_offsets(num_lines, offsets))
+		die("offsets must be unique");
+
 	chip = chip_open_lookup(argv[0]);
 	if (!chip)
 		die_perror("unable to open %s", argv[0]);
 
-	line_cfg = gpiod_line_config_new();
-	if (!line_cfg)
-		die_perror("unable to allocate the line config structure");
+	settings = gpiod_line_settings_new();
+	if (!settings)
+		die_perror("unable to allocate line settings");
 
 	if (bias)
-		gpiod_line_config_set_bias_default(line_cfg, bias);
+		gpiod_line_settings_set_bias(settings, bias);
 	if (active_low)
-		gpiod_line_config_set_active_low_default(line_cfg, true);
-	gpiod_line_config_set_edge_detection_default(line_cfg, edge);
+		gpiod_line_settings_set_active_low(settings, active_low);
+	gpiod_line_settings_set_edge_detection(settings, edge);
 
 	req_cfg = gpiod_request_config_new();
 	if (!req_cfg)
 		die_perror("unable to allocate the request config structure");
 
 	gpiod_request_config_set_consumer(req_cfg, "gpiomon");
-	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
+
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
+
+	ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
+						  num_lines, settings);
+	if (ret)
+		die_perror("unable to add line settings");
 
 	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
 	if (!request)
@@ -314,6 +326,7 @@ done:
 	gpiod_line_request_release(request);
 	gpiod_request_config_free(req_cfg);
 	gpiod_line_config_free(line_cfg);
+	gpiod_line_settings_free(settings);
 	gpiod_chip_close(chip);
 
 	return EXIT_SUCCESS;
diff --git a/tools/gpioset.c b/tools/gpioset.c
index 576b87d..290d1a3 100644
--- a/tools/gpioset.c
+++ b/tools/gpioset.c
@@ -191,6 +191,7 @@ int main(int argc, char **argv)
 {
 	const struct mode_mapping *mode = &modes[MODE_EXIT];
 	int ret, optc, opti, bias = 0, drive = 0, *values;
+	struct gpiod_line_settings *settings;
 	struct gpiod_request_config *req_cfg;
 	struct gpiod_line_request *request;
 	struct gpiod_line_config *line_cfg;
@@ -288,31 +289,44 @@ int main(int argc, char **argv)
 			die("invalid offset: %s", argv[i + 1]);
 	}
 
+	if (has_duplicate_offsets(num_lines, offsets))
+		die("offsets must be unique");
+
 	chip = chip_open_lookup(device);
 	if (!chip)
 		die_perror("unable to open %s", device);
 
-	line_cfg = gpiod_line_config_new();
-	if (!line_cfg)
-		die_perror("unable to allocate the line config structure");
+	settings = gpiod_line_settings_new();
+	if (!settings)
+		die_perror("unable to allocate line settings");
 
 	if (bias)
-		gpiod_line_config_set_bias_default(line_cfg, bias);
+		gpiod_line_settings_set_bias(settings, bias);
 	if (drive)
-		gpiod_line_config_set_drive_default(line_cfg, drive);
+		gpiod_line_settings_set_drive(settings, drive);
 	if (active_low)
-		gpiod_line_config_set_active_low_default(line_cfg, true);
-	gpiod_line_config_set_direction_default(line_cfg,
-						GPIOD_LINE_DIRECTION_OUTPUT);
-	gpiod_line_config_set_output_values(line_cfg, num_lines,
-					    offsets, values);
+		gpiod_line_settings_set_active_low(settings, active_low);
+	gpiod_line_settings_set_direction(settings,
+					  GPIOD_LINE_DIRECTION_OUTPUT);
 
 	req_cfg = gpiod_request_config_new();
 	if (!req_cfg)
 		die_perror("unable to allocate the request config structure");
 
 	gpiod_request_config_set_consumer(req_cfg, "gpioset");
-	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
+
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
+
+	for (i = 0; i < num_lines; i++) {
+		gpiod_line_settings_set_output_value(settings, values[i]);
+
+		ret = gpiod_line_config_add_line_settings(line_cfg, &offsets[i],
+							  1, settings);
+		if (ret)
+			die_perror("unable to add line settings");
+	}
 
 	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
 	if (!request)
@@ -324,6 +338,7 @@ int main(int argc, char **argv)
 	gpiod_line_request_release(request);
 	gpiod_request_config_free(req_cfg);
 	gpiod_line_config_free(line_cfg);
+	gpiod_line_settings_free(settings);
 	gpiod_chip_close(chip);
 	free(offsets);
 
diff --git a/tools/tools-common.c b/tools/tools-common.c
index f5fd50c..8957293 100644
--- a/tools/tools-common.c
+++ b/tools/tools-common.c
@@ -169,3 +169,16 @@ struct gpiod_chip *chip_open_lookup(const char *device)
 
 	return chip;
 }
+
+bool has_duplicate_offsets(size_t num_offsets, unsigned int *offsets)
+{
+	size_t i, j;
+
+	for (i = 0; i < num_offsets; i++) {
+		for (j = i + 1; j < num_offsets; j++)
+			if (offsets[i] == offsets[j])
+				return true;
+	}
+
+	return false;
+}
diff --git a/tools/tools-common.h b/tools/tools-common.h
index f059440..cb61d54 100644
--- a/tools/tools-common.h
+++ b/tools/tools-common.h
@@ -31,5 +31,6 @@ int make_signalfd(void);
 int chip_dir_filter(const struct dirent *entry);
 struct gpiod_chip *chip_open_by_name(const char *name);
 struct gpiod_chip *chip_open_lookup(const char *device);
+bool has_duplicate_offsets(size_t num_offsets, unsigned int *offsets);
 
 #endif /* __GPIOD_TOOLS_COMMON_H__ */
