diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index 552a258a6b849a2518fa6c83226cf9ab4e657717..c0c14338ddb3f1d0075df3b3d3d154bc1b5d678a 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -42,6 +42,8 @@
 #include "libcamera/internal/v4l2_subdevice.h"
 #include "libcamera/internal/v4l2_videodevice.h"
 
+#include "rzg2l-cru.h"
+
 namespace {
 
 bool isFormatRaw(const libcamera::PixelFormat &pixFmt)
@@ -95,6 +97,7 @@ public:
 	enum CameraType {
 		Tpg,
 		Inline,
+		Memory,
 	};
 
 	MaliC55CameraData(PipelineHandler *pipe)
@@ -107,13 +110,12 @@ public:
 	CameraType type() const { return type_; }
 
 	/* Deflect these functionalities to either TPG or CameraSensor. */
-	virtual int init(MediaEntity *entity) = 0;
-
 	virtual std::vector<Size> sizes(unsigned int mbusCode) const = 0;
 	virtual V4L2Subdevice *subdev() const = 0;
 	virtual CameraSensor *sensor() const = 0;
 	virtual V4L2Subdevice *csi2() const = 0;
 	virtual Size resolution() const = 0;
+	virtual RZG2LCRU *cru() const = 0;
 
 	int pixfmtToMbusCode(const PixelFormat &pixFmt) const;
 	const PixelFormat &bestRawFormat() const;
@@ -140,12 +142,34 @@ private:
 	std::string id_;
 };
 
-class MaliC55TpgCameraData : public MaliC55CameraData
+template<typename Derived>
+class MaliC55TemplatedCameraData : public MaliC55CameraData
+{
+public:
+	MaliC55TemplatedCameraData(PipelineHandler *pipe)
+		: MaliC55CameraData(pipe)
+	{
+	}
+
+	/*
+	 * The signature of the init() functions is different between the
+	 * Tpg, Inline and Memory camera use cases. Use CRTP to deflect to the
+	 * right implementation the templated argument and avoid polluting the
+	 * interface of the base class.
+	 */
+	template<typename MediaType>
+	int init(MediaType *media)
+	{
+		return static_cast<Derived *>(this)->init(media);
+	}
+};
+
+class MaliC55TpgCameraData : public MaliC55TemplatedCameraData<MaliC55TpgCameraData>
 {
 public:
 	MaliC55TpgCameraData(PipelineHandler *pipe);
 
-	int init(MediaEntity *entity) override;
+	int init(MediaEntity *entity);
 
 	std::vector<Size> sizes(unsigned int mbusCode) const override;
 
@@ -171,17 +195,23 @@ public:
 		return nullptr;
 	}
 
+	RZG2LCRU *cru() const override
+	{
+		ASSERT(false);
+		return nullptr;
+	}
+
 private:
 	Size resolution_;
 	std::unique_ptr<V4L2Subdevice> sd_;
 };
 
-class MaliC55InlineCameraData : public MaliC55CameraData
+class MaliC55InlineCameraData : public MaliC55TemplatedCameraData<MaliC55InlineCameraData>
 {
 public:
 	MaliC55InlineCameraData(PipelineHandler *pipe);
 
-	int init(MediaEntity *entity) override;
+	int init(MediaEntity *entity);
 
 	std::vector<Size> sizes(unsigned int mbusCode) const override
 	{
@@ -208,13 +238,62 @@ public:
 		return csi2_.get();
 	}
 
+	RZG2LCRU *cru() const override
+	{
+		ASSERT(false);
+		return nullptr;
+	}
+
 private:
 	std::unique_ptr<V4L2Subdevice> csi2_;
 	std::unique_ptr<CameraSensor> sensor_;
 };
 
+class MaliC55MemoryCameraData
+	: public MaliC55TemplatedCameraData<MaliC55MemoryCameraData>
+{
+public:
+	MaliC55MemoryCameraData(PipelineHandler *pipe);
+
+	int init(MediaDevice *cruMedia);
+
+	std::vector<Size>
+	sizes([[maybe_unused]] unsigned int mbusCode) const override
+	{
+		return cru_->sizes();
+	}
+
+	Size resolution() const override
+	{
+		return cru_->resolution();
+	}
+
+	V4L2Subdevice *subdev() const override
+	{
+		return cru_->sensor()->device();
+	}
+
+	CameraSensor *sensor() const override
+	{
+		return cru_->sensor();
+	}
+
+	V4L2Subdevice *csi2() const override
+	{
+		return cru_->csi2();
+	}
+
+	RZG2LCRU *cru() const override
+	{
+		return cru_.get();
+	}
+
+private:
+	std::unique_ptr<RZG2LCRU> cru_;
+};
+
 MaliC55TpgCameraData::MaliC55TpgCameraData(PipelineHandler *pipe)
-	: MaliC55CameraData(pipe)
+	: MaliC55TemplatedCameraData(pipe)
 {
 	type_ = CameraType::Tpg;
 }
@@ -267,7 +346,7 @@ std::vector<Size> MaliC55TpgCameraData::sizes(unsigned int mbusCode) const
 }
 
 MaliC55InlineCameraData::MaliC55InlineCameraData(PipelineHandler *pipe)
-	: MaliC55CameraData(pipe)
+	: MaliC55TemplatedCameraData(pipe)
 {
 	type_ = CameraType::Inline;
 }
@@ -292,6 +371,18 @@ int MaliC55InlineCameraData::init(MediaEntity *sensor)
 	return ret;
 }
 
+MaliC55MemoryCameraData::MaliC55MemoryCameraData(PipelineHandler *pipe)
+	: MaliC55TemplatedCameraData(pipe)
+{
+	type_ = CameraType::Memory;
+}
+
+int MaliC55MemoryCameraData::init(MediaDevice *cruMedia)
+{
+	cru_ = std::make_unique<RZG2LCRU>();
+	return cru_->init(cruMedia);
+}
+
 void MaliC55CameraData::setSensorControls(const ControlList &sensorControls)
 {
 	delayedCtrls_->push(sensorControls);
@@ -752,11 +843,15 @@ private:
 				const std::string &name);
 	bool registerTPGCamera(MediaLink *link);
 	bool registerSensorCamera(MediaLink *link);
+	bool registerMemoryInputCamera();
 
 	std::shared_ptr<MediaDevice> media_;
+	std::shared_ptr<MediaDevice> cruMedia_;
 	std::unique_ptr<V4L2Subdevice> isp_;
 	std::unique_ptr<V4L2VideoDevice> stats_;
 	std::unique_ptr<V4L2VideoDevice> params_;
+	std::unique_ptr<V4L2Subdevice> ivc_;
+	std::unique_ptr<V4L2VideoDevice> input_;
 
 	std::vector<std::unique_ptr<FrameBuffer>> statsBuffers_;
 	std::queue<FrameBuffer *> availableStatsBuffers_;
@@ -1009,6 +1104,8 @@ int PipelineHandlerMaliC55::configure(Camera *camera,
 		ret = csi2Entity->getPadByIndex(1)->links()[0]->setEnabled(true);
 		break;
 	}
+	case MaliC55CameraData::Memory:
+		break;
 	}
 	if (ret)
 		return ret;
@@ -1035,6 +1132,8 @@ int PipelineHandlerMaliC55::configure(Camera *camera,
 
 		ret = data->csi2()->getFormat(1, &subdevFormat);
 
+		break;
+	case MaliC55CameraData::Memory:
 		break;
 	}
 	}
@@ -1110,6 +1209,7 @@ int PipelineHandlerMaliC55::configure(Camera *camera,
 		pipe->stream = stream;
 	}
 
+	/* TPG doesn't support the IPA, so stop here. */
 	if (!data->ipa_)
 		return 0;
 
@@ -1651,13 +1751,14 @@ bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)
 		return true;
 	}
 
-	std::unique_ptr<MaliC55CameraData> data =
-		std::make_unique<MaliC55TpgCameraData>(this);
-
-	int ret = data->init(link->source()->entity());
-	if (ret)
+	MaliC55TpgCameraData *tpgData = new MaliC55TpgCameraData(this);
+	int ret = tpgData->init(link->source()->entity());
+	if (ret) {
+		delete tpgData;
 		return ret;
+	}
 
+	std::unique_ptr<MaliC55CameraData> data(tpgData);
 	return registerMaliCamera(std::move(data), name);
 }
 
@@ -1679,30 +1780,33 @@ bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
 		if (function != MEDIA_ENT_F_CAM_SENSOR)
 			continue;
 
-		std::unique_ptr<MaliC55CameraData> data =
-			std::make_unique<MaliC55InlineCameraData>(this);
-
-		int ret = data->init(sensor);
-		if (ret)
+		MaliC55InlineCameraData *inlineData =
+			new MaliC55InlineCameraData(this);
+		int ret = inlineData->init(sensor);
+		if (ret) {
+			delete inlineData;
 			return ret;
+		}
 
-		data->properties_ = data->sensor()->properties();
+		inlineData->properties_ = inlineData->sensor()->properties();
 
 		const CameraSensorProperties::SensorDelays &delays =
-			data->sensor()->sensorDelays();
+			inlineData->sensor()->sensorDelays();
 		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
 			{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
 		};
 
-		V4L2Subdevice *sensorSubdev = data->sensor()->device();
-		data->delayedCtrls_ = std::make_unique<DelayedControls>(sensorSubdev,
-									params);
-		isp_->frameStart.connect(data->delayedCtrls_.get(),
+		V4L2Subdevice *sensorSubdev = inlineData->sensor()->device();
+		inlineData->delayedCtrls_ =
+			std::make_unique<DelayedControls>(sensorSubdev,
+							  params);
+		isp_->frameStart.connect(inlineData->delayedCtrls_.get(),
 					 &DelayedControls::applyControls);
 
 		/* \todo Init properties. */
 
+		std::unique_ptr<MaliC55CameraData> data(inlineData);
 		if (!registerMaliCamera(std::move(data), sensor->name()))
 			return false;
 	}
@@ -1710,6 +1814,39 @@ bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
 	return true;
 }
 
+bool PipelineHandlerMaliC55::registerMemoryInputCamera()
+{
+	MaliC55MemoryCameraData *memoryData = new MaliC55MemoryCameraData(this);
+
+	int ret = memoryData->init(cruMedia_.get());
+	if (ret) {
+		delete memoryData;
+		return false;
+	}
+
+	CameraSensor *sensor = memoryData->sensor();
+	memoryData->properties_ = sensor->properties();
+
+	const CameraSensorProperties::SensorDelays &delays = sensor->sensorDelays();
+	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
+		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
+		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
+	};
+
+	memoryData->delayedCtrls_ =
+		std::make_unique<DelayedControls>(sensor->device(), params);
+	isp_->frameStart.connect(memoryData->delayedCtrls_.get(),
+				 &DelayedControls::applyControls);
+
+	input_->bufferReady.connect(memoryData->cru(), &RZG2LCRU::cruReturnBuffer);
+
+	std::unique_ptr<MaliC55CameraData> data(memoryData);
+	if (!registerMaliCamera(std::move(data), sensor->device()->entity()->name()))
+		return false;
+
+	return true;
+}
+
 bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)
 {
 	const MediaPad *ispSink;
@@ -1719,14 +1856,14 @@ bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)
 	 * The TPG and the downscale pipe are both optional blocks and may not
 	 * be fitted.
 	 */
-	DeviceMatch dm("mali-c55");
-	dm.add("mali-c55 isp");
-	dm.add("mali-c55 resizer fr");
-	dm.add("mali-c55 fr");
-	dm.add("mali-c55 3a stats");
-	dm.add("mali-c55 3a params");
-
-	media_ = acquireMediaDevice(enumerator, dm);
+	DeviceMatch c55_dm("mali-c55");
+	c55_dm.add("mali-c55 isp");
+	c55_dm.add("mali-c55 resizer fr");
+	c55_dm.add("mali-c55 fr");
+	c55_dm.add("mali-c55 3a stats");
+	c55_dm.add("mali-c55 3a params");
+
+	media_ = acquireMediaDevice(enumerator, c55_dm);
 	if (!media_)
 		return false;
 
@@ -1786,6 +1923,25 @@ bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)
 	stats_->bufferReady.connect(this, &PipelineHandlerMaliC55::statsBufferReady);
 	params_->bufferReady.connect(this, &PipelineHandlerMaliC55::paramsBufferReady);
 
+	/*
+	 * We also need to search for the rzg2l-cru CSI-2 receiver. If we find
+	 * that then we need to work in memory input mode instead of the inline
+	 * mode. The absence of this match is not necessarily a failure at this
+	 * point...it depends on the media links that we investigate momentarily.
+	 *
+	 * This is a bit hacky, because there could be multiple of these media
+	 * devices and we're just taking the first. We need modular pipelines to
+	 * properly solve the issue.
+	 */
+	static const std::regex cruCsi2Regex("csi-[0-9a-f]{8}.csi2");
+	static const std::regex cruIpRegex("cru-ip-[0-9a-f]{8}.cru[0-9]");
+
+	DeviceMatch cruDm("rzg2l_cru");
+	cruDm.add(cruCsi2Regex);
+	cruDm.add(cruIpRegex);
+	cruDm.add("CRU output");
+	cruMedia_ = acquireMediaDevice(enumerator, cruDm);
+
 	ispSink = isp_->entity()->getPadByIndex(0);
 	if (!ispSink || ispSink->links().empty()) {
 		LOG(MaliC55, Error) << "ISP sink pad error";
@@ -1799,13 +1955,6 @@ bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)
 	 * MEDIA_ENT_F_CAM_SENSOR - The test pattern generator
 	 * MEDIA_ENT_F_VID_IF_BRIDGE - A CSI-2 receiver
 	 * MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER - An input device
-	 *
-	 * The last one will be unsupported for now. The TPG is relatively easy,
-	 * we just register a Camera for it. If we have a CSI-2 receiver we need
-	 * to check its sink pad and register Cameras for anything connected to
-	 * it (probably...there are some complex situations in which that might
-	 * not be true but let's pretend they don't exist until we come across
-	 * them)
 	 */
 	bool registered;
 	for (MediaLink *link : ispSink->links()) {
@@ -1825,7 +1974,23 @@ bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)
 
 			break;
 		case MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER:
-			LOG(MaliC55, Warning) << "Memory input not yet supported";
+			if (!cruMedia_)
+				return false;
+
+			ivc_ = V4L2Subdevice::fromEntityName(media_.get(),
+							     "rzv2h ivc block");
+			if (ivc_->open() < 0)
+				return false;
+
+			input_ = V4L2VideoDevice::fromEntityName(media_.get(),
+								 "rzv2h-ivc");
+			if (input_->open() < 0)
+				return false;
+
+			registered = registerMemoryInputCamera();
+			if (!registered)
+				return registered;
+
 			break;
 		default:
 			LOG(MaliC55, Error) << "Unsupported entity function";
