diff --git a/include/libcamera/internal/converter.h b/include/libcamera/internal/converter.h
index 652ff519..853888f4 100644
--- a/include/libcamera/internal/converter.h
+++ b/include/libcamera/internal/converter.h
@@ -14,6 +14,7 @@
 #include <memory>
 #include <string>
 #include <tuple>
+#include <utility>
 #include <vector>
 
 #include <libcamera/base/class.h>
@@ -35,6 +36,7 @@ class Converter
 public:
 	enum class Feature {
 		None = 0,
+		InputCrop = (1 << 0),
 	};
 
 	using Features = Flags<Feature>;
@@ -63,6 +65,9 @@ public:
 	virtual int queueBuffers(FrameBuffer *input,
 				 const std::map<const Stream *, FrameBuffer *> &outputs) = 0;
 
+	virtual int setInputCrop(const Stream *stream, Rectangle *rect);
+	virtual std::pair<Rectangle, Rectangle> inputCropBounds(const Stream *stream);
+
 	Signal<FrameBuffer *> inputBufferReady;
 	Signal<FrameBuffer *> outputBufferReady;
 
diff --git a/include/libcamera/internal/converter/converter_v4l2_m2m.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h
index 91701dbe..5b69b14e 100644
--- a/include/libcamera/internal/converter/converter_v4l2_m2m.h
+++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h
@@ -30,6 +30,7 @@ class Size;
 class SizeRange;
 class Stream;
 struct StreamConfiguration;
+class Rectangle;
 class V4L2M2MDevice;
 
 class V4L2M2MConverter : public Converter
@@ -57,6 +58,9 @@ public:
 	int queueBuffers(FrameBuffer *input,
 			 const std::map<const Stream *, FrameBuffer *> &outputs);
 
+	int setInputCrop(const Stream *stream, Rectangle *rect);
+	std::pair<Rectangle, Rectangle> inputCropBounds(const Stream *stream);
+
 private:
 	class V4L2M2MStream : protected Loggable
 	{
@@ -75,6 +79,8 @@ private:
 
 		int queueBuffers(FrameBuffer *input, FrameBuffer *output);
 
+		int setSelection(unsigned int target, Rectangle *rect);
+
 	protected:
 		std::string logPrefix() const override;
 
diff --git a/src/libcamera/converter.cpp b/src/libcamera/converter.cpp
index e2dbf5e0..7ab56b4c 100644
--- a/src/libcamera/converter.cpp
+++ b/src/libcamera/converter.cpp
@@ -11,6 +11,8 @@
 
 #include <libcamera/base/log.h>
 
+#include <libcamera/stream.h>
+
 #include "libcamera/internal/media_device.h"
 
 /**
@@ -39,6 +41,8 @@ LOG_DEFINE_CATEGORY(Converter)
  * \brief Specify the features supported by the converter
  * \var Converter::Feature::None
  * \brief No extra features supported by the converter
+ * \var Converter::Feature::InputCrop
+ * \brief Cropping capability at input is supported by the converter
  */
 
 /**
@@ -161,6 +165,54 @@ Converter::~Converter()
  * \return 0 on success or a negative error code otherwise
  */
 
+/**
+ * \brief Set the crop rectangle \a rect for \a stream
+ * \param[in] stream Pointer to output stream
+ * \param[inout] rect The crop rectangle to be applied
+ *
+ * Set the crop rectangle \a rect for \a stream provided the converter supports
+ * cropping. The converter should have the Feature::InputCrop flag in this
+ * case.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int Converter::setInputCrop([[maybe_unused]] const Stream *stream, [[maybe_unused]] Rectangle *rect)
+{
+	if (!(features() & Feature::InputCrop)) {
+		LOG(Converter, Error) << "Converter doesn't support cropping capabilities";
+		return -ENOTSUP;
+	}
+
+	return 0;
+}
+
+/**
+ * \brief Get the crop bounds \a stream
+ * \param[in] stream Pointer to output stream
+ *
+ * Get the minimum and maximum crop bounds for \a stream. The converter
+ * should supporting cropping (Feature::InputCrop).
+ *
+ * \return A std::pair<Rectangle, Rectangle> containining minimum and maximum
+ * crop bound respectively.
+ */
+std::pair<Rectangle, Rectangle> Converter::inputCropBounds([[maybe_unused]] const Stream *stream)
+{
+	const StreamConfiguration &config = stream->configuration();
+	Rectangle rect;
+
+	if (!(features() & Feature::InputCrop))
+		LOG(Converter, Error) << "Converter doesn't support cropping capabilities";
+
+	/*
+	 * This is base implementation for the Converter class, so just return
+	 * the stream configured size as minimum and maximum crop bounds.
+	 */
+	rect.size() = config.size;
+
+	return { rect, rect };
+}
+
 /**
  * \var Converter::inputBufferReady
  * \brief A signal emitted when the input frame buffer completes
diff --git a/src/libcamera/converter/converter_v4l2_m2m.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
index 4aeb7dd9..3295b988 100644
--- a/src/libcamera/converter/converter_v4l2_m2m.cpp
+++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
@@ -155,6 +155,15 @@ int V4L2M2MConverter::V4L2M2MStream::queueBuffers(FrameBuffer *input, FrameBuffe
 	return 0;
 }
 
+int V4L2M2MConverter::V4L2M2MStream::setSelection(unsigned int target, Rectangle *rect)
+{
+	int ret = m2m_->output()->setSelection(target, rect);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
 std::string V4L2M2MConverter::V4L2M2MStream::logPrefix() const
 {
 	return stream_->configuration().toString();
@@ -375,6 +384,85 @@ int V4L2M2MConverter::exportBuffers(const Stream *stream, unsigned int count,
 	return iter->second->exportBuffers(count, buffers);
 }
 
+/**
+ * \copydoc libcamera::Converter::setInputCrop
+ */
+int V4L2M2MConverter::setInputCrop(const Stream *stream, Rectangle *rect)
+{
+	if (!(features() & Feature::InputCrop))
+		return -ENOTSUP;
+
+	auto iter = streams_.find(stream);
+	if (iter == streams_.end())
+		return -EINVAL;
+
+	return iter->second->setSelection(V4L2_SEL_TGT_CROP, rect);
+}
+
+/**
+ * \copydoc libcamera::Converter::inputCropBounds
+ */
+std::pair<Rectangle, Rectangle>
+V4L2M2MConverter::inputCropBounds(const Stream *stream)
+{
+	Rectangle minCrop;
+	Rectangle maxCrop;
+	int ret;
+
+	if (!(features() & Feature::InputCrop)) {
+		LOG(Converter, Error) << "Input Crop functionality is not supported";
+		return {};
+	}
+
+	minCrop.width = 1;
+	minCrop.height = 1;
+	maxCrop.width = UINT_MAX;
+	maxCrop.height = UINT_MAX;
+
+	auto iter = streams_.find(stream);
+	if (iter == streams_.end()) {
+		/*
+		 * No streams configured, return minimum and maximum crop
+		 * bounds at initialization.
+		 */
+		ret = m2m_->output()->setSelection(V4L2_SEL_TGT_CROP, &minCrop);
+		if (ret) {
+			LOG(Converter, Error) << "Failed to query minimum crop bound"
+					      << strerror(-ret);
+			return {};
+		}
+
+		ret = m2m_->output()->setSelection(V4L2_SEL_TGT_CROP, &maxCrop);
+		if (ret) {
+			LOG(Converter, Error) << "Failed to query maximum crop bound"
+					      << strerror(-ret);
+			return {};
+		}
+
+		return { minCrop, maxCrop };
+	}
+
+	/*
+	 * If the streams are configured, return bounds from according to
+	 * stream configuration.
+	 */
+	ret = setInputCrop(stream, &minCrop);
+	if (ret) {
+		LOG(Converter, Error) << "Failed to query minimum crop bound"
+				      << strerror(-ret);
+		return {};
+	}
+
+	ret = setInputCrop(stream, &maxCrop);
+	if (ret) {
+		LOG(Converter, Error) << "Failed to query maximum crop bound"
+				      << strerror(-ret);
+		return {};
+	}
+
+	return { minCrop, maxCrop };
+}
+
 /**
  * \copydoc libcamera::Converter::start
  */
@@ -448,6 +536,10 @@ int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
 	return 0;
 }
 
+/*
+ * \todo: This should be extended to include Feature::Flag to denote
+ * what each converter supports feature-wise.
+ */
 static std::initializer_list<std::string> compatibles = {
 	"mtk-mdp",
 	"pxp",
