diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index cf0cb15f8bb39143eea38aa8acb8d2b1268f5530..801067ce00fe4e0fd6b81db699fcaed2ebb840b6 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -92,17 +92,22 @@ struct MaliC55FrameInfo {
 class MaliC55CameraData : public Camera::Private
 {
 public:
-	MaliC55CameraData(PipelineHandler *pipe, MediaEntity *entity)
-		: Camera::Private(pipe), entity_(entity)
+	enum CameraType {
+		TPG,
+		Inline,
+	};
+
+	MaliC55CameraData(PipelineHandler *pipe)
+		: Camera::Private(pipe)
 	{
 	}
 
-	int init();
 	int loadIPA();
 
 	/* Deflect these functionalities to either TPG or CameraSensor. */
 	std::vector<Size> sizes(unsigned int mbusCode) const;
 	Size resolution() const;
+	V4L2Subdevice *subdev() const;
 
 	int pixfmtToMbusCode(const PixelFormat &pixFmt) const;
 	const PixelFormat &bestRawFormat() const;
@@ -112,11 +117,9 @@ public:
 	PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const;
 	Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const;
 
-	std::unique_ptr<CameraSensor> sensor_;
+	int initTPG(MediaEntity *tpg);
+	int initInlineCamera(MediaEntity *sensor);
 
-	MediaEntity *entity_;
-	std::unique_ptr<V4L2Subdevice> csi_;
-	std::unique_ptr<V4L2Subdevice> sd_;
 	Stream frStream_;
 	Stream dsStream_;
 
@@ -126,68 +129,71 @@ public:
 
 	std::unique_ptr<DelayedControls> delayedCtrls_;
 
+	struct {
+		Size resolution_;
+		std::unique_ptr<V4L2Subdevice> sd_;
+	} tpgInput;
+
+	struct {
+		std::unique_ptr<V4L2Subdevice> csi2_;
+		std::unique_ptr<CameraSensor> sensor_;
+	} inlineInput;
+
+	CameraType input_;
+
 private:
-	void initTPGData();
 	void setSensorControls(const ControlList &sensorControls);
-
 	std::string id_;
-	Size tpgResolution_;
 };
 
-int MaliC55CameraData::init()
+int MaliC55CameraData::initTPG(MediaEntity *tpg)
 {
-	int ret;
-
-	sd_ = std::make_unique<V4L2Subdevice>(entity_);
-	ret = sd_->open();
+	tpgInput.sd_ = std::make_unique<V4L2Subdevice>(tpg);
+	int ret = tpgInput.sd_->open();
 	if (ret) {
-		LOG(MaliC55, Error) << "Failed to open sensor subdevice";
+		LOG(MaliC55, Error) << "Failed to open TPG subdevice";
 		return ret;
 	}
 
-	/* If this camera is created from TPG, we return here. */
-	if (entity_->name() == "mali-c55 tpg") {
-		initTPGData();
-		return 0;
-	}
-
-	/*
-	 * Register a CameraSensor if we connect to a sensor and create
-	 * an entity for the connected CSI-2 receiver.
-	 */
-	sensor_ = CameraSensorFactoryBase::create(entity_);
-	if (!sensor_)
-		return -ENODEV;
+	/* Replicate the CameraSensor implementation for TPG. */
+	V4L2Subdevice::Formats formats = tpgInput.sd_->formats(0);
+	if (formats.empty())
+		return -EINVAL;
 
-	const MediaPad *sourcePad = entity_->getPadByIndex(0);
-	MediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity();
+	std::vector<Size> tpgSizes;
 
-	csi_ = std::make_unique<V4L2Subdevice>(csiEntity);
-	ret = csi_->open();
-	if (ret) {
-		LOG(MaliC55, Error) << "Failed to open CSI-2 subdevice";
-		return ret;
+	for (const auto &format : formats) {
+		const std::vector<SizeRange> &ranges = format.second;
+		std::transform(ranges.begin(), ranges.end(), std::back_inserter(tpgSizes),
+			       [](const SizeRange &range) { return range.max; });
 	}
 
+	tpgInput.resolution_ = tpgSizes.back();
+	input_ = TPG;
+
 	return 0;
 }
 
-void MaliC55CameraData::initTPGData()
+int MaliC55CameraData::initInlineCamera(MediaEntity *sensor)
 {
-	/* Replicate the CameraSensor implementation for TPG. */
-	V4L2Subdevice::Formats formats = sd_->formats(0);
-	if (formats.empty())
-		return;
+	/* Register a CameraSensor create an entity for the CSI-2 receiver. */
+	inlineInput.sensor_ = CameraSensorFactoryBase::create(sensor);
+	if (!inlineInput.sensor_)
+		return -ENODEV;
 
-	std::vector<Size> tpgSizes;
+	const MediaPad *sourcePad = sensor->getPadByIndex(0);
+	MediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity();
 
-	for (const auto &format : formats) {
-		const std::vector<SizeRange> &ranges = format.second;
-		std::transform(ranges.begin(), ranges.end(), std::back_inserter(tpgSizes),
-			       [](const SizeRange &range) { return range.max; });
+	inlineInput.csi2_ = std::make_unique<V4L2Subdevice>(csiEntity);
+	int ret = inlineInput.csi2_->open();
+	if (ret) {
+		LOG(MaliC55, Error) << "Failed to open CSI-2 subdevice";
+		return ret;
 	}
 
-	tpgResolution_ = tpgSizes.back();
+	input_ = Inline;
+
+	return 0;
 }
 
 void MaliC55CameraData::setSensorControls(const ControlList &sensorControls)
@@ -197,10 +203,10 @@ void MaliC55CameraData::setSensorControls(const ControlList &sensorControls)
 
 std::vector<Size> MaliC55CameraData::sizes(unsigned int mbusCode) const
 {
-	if (sensor_)
-		return sensor_->sizes(mbusCode);
+	if (input_ == Inline)
+		return inlineInput.sensor_->sizes(mbusCode);
 
-	V4L2Subdevice::Formats formats = sd_->formats(0);
+	V4L2Subdevice::Formats formats = tpgInput.sd_->formats(0);
 	if (formats.empty())
 		return {};
 
@@ -220,10 +226,28 @@ std::vector<Size> MaliC55CameraData::sizes(unsigned int mbusCode) const
 
 Size MaliC55CameraData::resolution() const
 {
-	if (sensor_)
-		return sensor_->resolution();
+	switch (input_) {
+	case TPG:
+		return tpgInput.resolution_;
+	case Inline:
+		return inlineInput.sensor_->resolution();
+	}
+
+	assert(false);
+	return {};
+}
+
+V4L2Subdevice *MaliC55CameraData::subdev() const
+{
+	switch (input_) {
+	case TPG:
+		return tpgInput.sd_.get();
+	case Inline:
+		return inlineInput.sensor_->device();
+	}
 
-	return tpgResolution_;
+	assert(false);
+	return nullptr;
 }
 
 /*
@@ -242,7 +266,8 @@ int MaliC55CameraData::pixfmtToMbusCode(const PixelFormat &pixFmt) const
 	if (!bayerFormat.isValid())
 		return -EINVAL;
 
-	V4L2Subdevice::Formats formats = sd_->formats(0);
+	V4L2Subdevice *sd = subdev();
+	V4L2Subdevice::Formats formats = sd->formats(0);
 	unsigned int sensorMbusCode = 0;
 	unsigned int bitDepth = 0;
 
@@ -280,7 +305,8 @@ const PixelFormat &MaliC55CameraData::bestRawFormat() const
 {
 	static const PixelFormat invalidPixFmt = {};
 
-	for (const auto &fmt : sd_->formats(0)) {
+	V4L2Subdevice *sd = subdev();
+	for (const auto &fmt : sd->formats(0)) {
 		BayerFormat sensorBayer = BayerFormat::fromMbusCode(fmt.first);
 
 		if (!sensorBayer.isValid())
@@ -302,11 +328,11 @@ const PixelFormat &MaliC55CameraData::bestRawFormat() const
 
 void MaliC55CameraData::updateControls(const ControlInfoMap &ipaControls)
 {
-	if (!sensor_)
+	if (input_ == TPG)
 		return;
 
 	IPACameraSensorInfo sensorInfo;
-	int ret = sensor_->sensorInfo(&sensorInfo);
+	int ret = inlineInput.sensor_->sensorInfo(&sensorInfo);
 	if (ret) {
 		LOG(MaliC55, Error) << "Failed to retrieve sensor info";
 		return;
@@ -379,7 +405,7 @@ int MaliC55CameraData::loadIPA()
 	int ret;
 
 	/* Do not initialize IPA for TPG. */
-	if (!sensor_)
+	if (input_ == TPG)
 		return 0;
 
 	ipa_ = IPAManager::createIPA<ipa::mali_c55::IPAProxyMaliC55>(pipe(), 1, 1);
@@ -388,20 +414,21 @@ int MaliC55CameraData::loadIPA()
 
 	ipa_->setSensorControls.connect(this, &MaliC55CameraData::setSensorControls);
 
-	std::string ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml",
+	CameraSensor *sensor = inlineInput.sensor_.get();
+	std::string ipaTuningFile = ipa_->configurationFile(sensor->model() + ".yaml",
 							    "uncalibrated.yaml");
 
 	/* We need to inform the IPA of the sensor configuration */
 	ipa::mali_c55::IPAConfigInfo ipaConfig{};
 
-	ret = sensor_->sensorInfo(&ipaConfig.sensorInfo);
+	ret = sensor->sensorInfo(&ipaConfig.sensorInfo);
 	if (ret)
 		return ret;
 
-	ipaConfig.sensorControls = sensor_->controls();
+	ipaConfig.sensorControls = sensor->controls();
 
 	ControlInfoMap ipaControls;
-	ret = ipa_->init({ ipaTuningFile, sensor_->model() }, ipaConfig,
+	ret = ipa_->init({ ipaTuningFile, sensor->model() }, ipaConfig,
 			 &ipaControls);
 	if (ret) {
 		LOG(MaliC55, Error) << "Failed to initialise the Mali-C55 IPA";
@@ -444,13 +471,14 @@ CameraConfiguration::Status MaliC55CameraConfiguration::validate()
 	 * The TPG doesn't support flips, so we only need to calculate a
 	 * transform if we have a sensor.
 	 */
-	if (data_->sensor_) {
+	if (data_->input_ == MaliC55CameraData::TPG) {
+		combinedTransform_ = Transform::Rot0;
+	} else {
 		Orientation requestedOrientation = orientation;
-		combinedTransform_ = data_->sensor_->computeTransform(&orientation);
+		combinedTransform_ =
+			data_->inlineInput.sensor_->computeTransform(&orientation);
 		if (orientation != requestedOrientation)
 			status = Adjusted;
-	} else {
-		combinedTransform_ = Transform::Rot0;
 	}
 
 	/* Only 2 streams available. */
@@ -927,11 +955,17 @@ int PipelineHandlerMaliC55::configure(Camera *camera,
 
 	/* Link the graph depending if we are operating the TPG or a sensor. */
 	MaliC55CameraData *data = cameraData(camera);
-	if (data->csi_) {
-		const MediaEntity *csiEntity = data->csi_->entity();
-		ret = csiEntity->getPadByIndex(1)->links()[0]->setEnabled(true);
-	} else {
-		ret = data->entity_->getPadByIndex(0)->links()[0]->setEnabled(true);
+	switch (data->input_) {
+	case MaliC55CameraData::TPG: {
+		const MediaEntity *tpgEntity = data->tpgInput.sd_->entity();
+		ret = tpgEntity->getPadByIndex(0)->links()[0]->setEnabled(true);
+		break;
+	}
+	case MaliC55CameraData::Inline: {
+		const MediaEntity *csi2Entity = data->inlineInput.csi2_->entity();
+		ret = csi2Entity->getPadByIndex(1)->links()[0]->setEnabled(true);
+		break;
+	}
 	}
 	if (ret)
 		return ret;
@@ -939,26 +973,28 @@ int PipelineHandlerMaliC55::configure(Camera *camera,
 	MaliC55CameraConfiguration *maliConfig =
 		static_cast<MaliC55CameraConfiguration *>(config);
 	V4L2SubdeviceFormat subdevFormat = maliConfig->sensorFormat_;
-	ret = data->sd_->getFormat(0, &subdevFormat);
-	if (ret)
-		return ret;
 
-	if (data->sensor_) {
-		ret = data->sensor_->setFormat(&subdevFormat,
-					       maliConfig->combinedTransform());
+	/* Apply format to the origin of the pipeline and propagate it. */
+	switch (data->input_) {
+	case MaliC55CameraData::TPG:
+		ret = data->tpgInput.sd_->setFormat(0, &subdevFormat);
+		break;
+	case MaliC55CameraData::Inline:
+		ret = data->inlineInput.sensor_->setFormat(&subdevFormat,
+							   maliConfig->combinedTransform());
 		if (ret)
 			return ret;
-	}
 
-	if (data->csi_) {
-		ret = data->csi_->setFormat(0, &subdevFormat);
+		ret = data->inlineInput.csi2_->setFormat(0, &subdevFormat);
 		if (ret)
 			return ret;
 
-		ret = data->csi_->getFormat(1, &subdevFormat);
-		if (ret)
-			return ret;
+		ret = data->inlineInput.csi2_->getFormat(1, &subdevFormat);
+
+		break;
 	}
+	if (ret)
+		return ret;
 
 	V4L2DeviceFormat statsFormat;
 	ret = stats_->getFormat(&statsFormat);
@@ -973,8 +1009,6 @@ int PipelineHandlerMaliC55::configure(Camera *camera,
 	/*
 	 * Propagate the format to the ISP sink pad and configure the input
 	 * crop rectangle (no crop at the moment).
-	 *
-	 * \todo Configure the CSI-2 receiver.
 	 */
 	ret = isp_->setFormat(0, &subdevFormat);
 	if (ret)
@@ -1058,18 +1092,19 @@ int PipelineHandlerMaliC55::configure(Camera *camera,
 	/* We need to inform the IPA of the sensor configuration */
 	ipa::mali_c55::IPAConfigInfo ipaConfig{};
 
-	ret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo);
+	ret = data->inlineInput.sensor_->sensorInfo(&ipaConfig.sensorInfo);
 	if (ret)
 		return ret;
 
-	ipaConfig.sensorControls = data->sensor_->controls();
+	ipaConfig.sensorControls = data->inlineInput.sensor_->controls();
 
 	/*
 	 * And we also need to tell the IPA the bayerOrder of the data (as
 	 * affected by any flips that we've configured)
 	 */
 	const Transform &combinedTransform = maliConfig->combinedTransform();
-	BayerFormat::Order bayerOrder = data->sensor_->bayerOrder(combinedTransform);
+	BayerFormat::Order bayerOrder =
+		data->inlineInput.sensor_->bayerOrder(combinedTransform);
 
 	ControlInfoMap ipaControls;
 	ret = data->ipa_->configure(ipaConfig, utils::to_underlying(bayerOrder),
@@ -1283,7 +1318,7 @@ void PipelineHandlerMaliC55::applyScalerCrop(Camera *camera,
 	if (!scalerCrop)
 		return;
 
-	if (!data->sensor_) {
+	if (data->input_ == MaliC55CameraData::TPG) {
 		LOG(MaliC55, Error) << "ScalerCrop not supported for TPG";
 		return;
 	}
@@ -1291,7 +1326,7 @@ void PipelineHandlerMaliC55::applyScalerCrop(Camera *camera,
 	Rectangle nativeCrop = *scalerCrop;
 
 	IPACameraSensorInfo sensorInfo;
-	int ret = data->sensor_->sensorInfo(&sensorInfo);
+	int ret = data->inlineInput.sensor_->sensorInfo(&sensorInfo);
 	if (ret) {
 		LOG(MaliC55, Error) << "Failed to retrieve sensor info";
 		return;
@@ -1572,10 +1607,8 @@ bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)
 		return true;
 	}
 
-	std::unique_ptr<MaliC55CameraData> data =
-		std::make_unique<MaliC55CameraData>(this, link->source()->entity());
-
-	if (data->init())
+	std::unique_ptr<MaliC55CameraData> data = std::make_unique<MaliC55CameraData>(this);
+	if (data->initTPG(link->source()->entity()))
 		return false;
 
 	return registerMaliCamera(std::move(data), name);
@@ -1600,21 +1633,22 @@ bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
 			continue;
 
 		std::unique_ptr<MaliC55CameraData> data =
-			std::make_unique<MaliC55CameraData>(this, sensor);
-		if (data->init())
+			std::make_unique<MaliC55CameraData>(this);
+		if (data->initInlineCamera(sensor))
 			return false;
 
-		data->properties_ = data->sensor_->properties();
+		data->properties_ = data->inlineInput.sensor_->properties();
 
-		const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays();
+		const CameraSensorProperties::SensorDelays &delays =
+			data->inlineInput.sensor_->sensorDelays();
 		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
 			{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
 		};
 
-		data->delayedCtrls_ =
-			std::make_unique<DelayedControls>(data->sensor_->device(),
-							  params);
+		V4L2Subdevice *sensorSubdev = data->inlineInput.sensor_->device();
+		data->delayedCtrls_ = std::make_unique<DelayedControls>(sensorSubdev,
+									params);
 		isp_->frameStart.connect(data->delayedCtrls_.get(),
 					 &DelayedControls::applyControls);
 
