diff --git a/include/libcamera/internal/converter.h b/include/libcamera/internal/converter.h
index 6623de4d..ffbb6f34 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) = 0;
+	virtual std::pair<Rectangle, Rectangle> inputCropBounds(const Stream *stream) = 0;
+
 	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 b9e59899..0bc0d053 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,11 @@ private:
 
 		int queueBuffers(FrameBuffer *input, FrameBuffer *output);
 
+		int setInputSelection(unsigned int target, Rectangle *rect);
+		int getInputSelection(unsigned int target, Rectangle *rect);
+
+		std::pair<Rectangle, Rectangle> inputCropBounds();
+
 	protected:
 		std::string logPrefix() const override;
 
@@ -88,6 +97,8 @@ private:
 
 		unsigned int inputBufferCount_;
 		unsigned int outputBufferCount_;
+
+		std::pair<Rectangle, Rectangle> inputCropBounds_;
 	};
 
 	std::unique_ptr<V4L2M2MDevice> m2m_;
diff --git a/src/libcamera/converter.cpp b/src/libcamera/converter.cpp
index d7bb7273..945f2527 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,39 @@ Converter::~Converter()
  * \return 0 on success or a negative error code otherwise
  */
 
+/**
+ * \fn Converter::setInputCrop()
+ * \brief Set the crop rectangle \a rect for \a stream
+ * \param[in] stream The output stream
+ * \param[inout] rect The crop rectangle to apply and return the rectangle
+ * that is actually applied
+ *
+ * Set the crop rectangle \a rect for \a stream provided the converter supports
+ * cropping. The converter has the Feature::InputCrop flag in this case.
+ *
+ * The underlying hardware can adjust the rectangle supplied by the user
+ * due to hardware constraints. The caller can inspect \a rect to determine the
+ * actual rectangle that has been applied by the converter, after this function
+ * returns.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+
+/**
+ * \fn Converter::inputCropBounds()
+ * \brief Retrieve the crop bounds for \a stream
+ * \param[in] stream The output stream
+ *
+ * Retrieve the minimum and maximum crop bounds for \a stream. The converter
+ * should support cropping (Feature::InputCrop).
+ *
+ * The crop bounds depend on the configuration of the output stream and hence
+ * this function should be called after the \a stream has been configured using
+ * configure().
+ *
+ * \return A pair containing the minimum and maximum crop bound in that order
+ */
+
 /**
  * \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 4f3e8ce4..d63ef2f8 100644
--- a/src/libcamera/converter/converter_v4l2_m2m.cpp
+++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
@@ -97,6 +97,44 @@ int V4L2M2MConverter::V4L2M2MStream::configure(const StreamConfiguration &inputC
 	inputBufferCount_ = inputCfg.bufferCount;
 	outputBufferCount_ = outputCfg.bufferCount;
 
+	if (converter_->features() & Feature::InputCrop) {
+		Rectangle minCrop;
+		Rectangle maxCrop;
+
+		/* Find crop bounds */
+		minCrop.width = 1;
+		minCrop.height = 1;
+		maxCrop.width = UINT_MAX;
+		maxCrop.height = UINT_MAX;
+
+		ret = setInputSelection(V4L2_SEL_TGT_CROP, &minCrop);
+		if (ret) {
+			LOG(Converter, Error)
+				<< "Could not query minimum selection crop: "
+				<< strerror(-ret);
+			return ret;
+		}
+
+		ret = getInputSelection(V4L2_SEL_TGT_CROP_BOUNDS, &maxCrop);
+		if (ret) {
+			LOG(Converter, Error)
+				<< "Could not query maximum selection crop: "
+				<< strerror(-ret);
+			return ret;
+		}
+
+		/* Reset the crop to its maximum */
+		ret = setInputSelection(V4L2_SEL_TGT_CROP, &maxCrop);
+		if (ret) {
+			LOG(Converter, Error)
+				<< "Could not reset selection crop: "
+				<< strerror(-ret);
+			return ret;
+		}
+
+		inputCropBounds_ = { minCrop, maxCrop };
+	}
+
 	return 0;
 }
 
@@ -154,6 +192,21 @@ int V4L2M2MConverter::V4L2M2MStream::queueBuffers(FrameBuffer *input, FrameBuffe
 	return 0;
 }
 
+int V4L2M2MConverter::V4L2M2MStream::getInputSelection(unsigned int target, Rectangle *rect)
+{
+	return m2m_->output()->getSelection(target, rect);
+}
+
+int V4L2M2MConverter::V4L2M2MStream::setInputSelection(unsigned int target, Rectangle *rect)
+{
+	return m2m_->output()->setSelection(target, rect);
+}
+
+std::pair<Rectangle, Rectangle> V4L2M2MConverter::V4L2M2MStream::inputCropBounds()
+{
+	return inputCropBounds_;
+}
+
 std::string V4L2M2MConverter::V4L2M2MStream::logPrefix() const
 {
 	return stream_->configuration().toString();
@@ -204,6 +257,33 @@ V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)
 		m2m_.reset();
 		return;
 	}
+
+	/* Discover Feature::InputCrop */
+	Rectangle maxCrop;
+	maxCrop.width = UINT_MAX;
+	maxCrop.height = UINT_MAX;
+
+	ret = m2m_->output()->setSelection(V4L2_SEL_TGT_CROP, &maxCrop);
+	if (ret)
+		return;
+
+	/*
+	 * Rectangles for cropping targets are defined even if the device
+	 * does not support cropping. Their sizes and positions will be
+	 * fixed in such cases.
+	 *
+	 * Set and inspect a crop equivalent to half of the maximum crop
+	 * returned earlier. Use this to determine whether the crop on
+	 * input is really supported.
+	 */
+	Rectangle halfCrop(maxCrop.size() / 2);
+	ret = m2m_->output()->setSelection(V4L2_SEL_TGT_CROP, &halfCrop);
+	if (!ret && halfCrop != maxCrop) {
+		features_ |= Feature::InputCrop;
+
+		LOG(Converter, Info)
+			<< "Converter supports cropping on its input";
+	}
 }
 
 /**
@@ -373,6 +453,36 @@ 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()) {
+		LOG(Converter, Error) << "Invalid output stream";
+		return -EINVAL;
+	}
+
+	return iter->second->setInputSelection(V4L2_SEL_TGT_CROP, rect);
+}
+
+/**
+ * \copydoc libcamera::Converter::inputCropBounds
+ */
+std::pair<Rectangle, Rectangle>
+V4L2M2MConverter::inputCropBounds(const Stream *stream)
+{
+	auto iter = streams_.find(stream);
+	if (iter == streams_.end())
+		return {};
+
+	return iter->second->inputCropBounds();
+}
+
 /**
  * \copydoc libcamera::Converter::start
  */
