From 863d7bc8eca3bab5292641cb92a01af3cc0591e9 Mon Sep 17 00:00:00 2001 From: Bert Outtier Date: Tue, 27 Oct 2026 12:06:15 +0100 Subject: [PATCH] pipeline: rpi: pisp: Support LSB-aligned unpacked RAW streams The PiSP Front End can shift its 16-bit output right, and the Back End can shift its input left by the same amount (the SHIFT field of PISP_FE_OUTPUT0_FORMAT and PISP_BE_INPUT_FORMAT). That writes unpacked 10 to 14-bit samples aligned to the LSB, the layout of the unpacked formats such as formats::SBGGR10 and SBGGR12, which some applications want without the cost of shifting every frame on the CPU. Derive the shift of unpacked Bayer formats with fewer than 16 bits from their bit depth in toPiSPImageFormat(), the way the compressed formats give the compression mode: 16 - bit depth. Keep an unpacked RAW stream request at the sensor bit depth as the CFE device format when the CFE Front End output offers it. The Back End input format comes from the same device format, so the ISP outputs keep working alongside the RAW stream. The CFE needs to offer the unpacked formats on its Front End output (rp1_cfe in the Raspberry Pi kernel). Without that, the request falls back to the unpacked 16-bit format as before. Tested on a Raspberry Pi 5 with a 10-bit and a 12-bit sensor: the RAW stream is the 16-bit one shifted down by 6 and 4 bits, and the ISP output is bit-identical with and without the shift; with an unpatched kernel, the stream falls back to 16 bits. Signed-off-by: Bert Outtier --- src/libcamera/pipeline/rpi/pisp/pisp.cpp | 29 ++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp index b37903163..f72e0f547 100644 --- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp @@ -140,8 +140,14 @@ pisp_image_format_config toPiSPImageFormat(V4L2DeviceFormat &format) BayerFormat bayer = BayerFormat::fromPixelFormat(pix); switch (bayer.packing) { case BayerFormat::Packing::None: + /* + * Unpacked samples with fewer than 16 bits are aligned + * to the LSB: the Frontend output shifts them right, + * the Backend input shifts them back left. + */ image.format = PISP_IMAGE_FORMAT_BPS_16 + - PISP_IMAGE_FORMAT_UNCOMPRESSED; + PISP_IMAGE_FORMAT_UNCOMPRESSED + + (16 - bayer.bitDepth) * PISP_IMAGE_FORMAT_SHIFT_1; break; case BayerFormat::Packing::PISP1: image.format = PISP_IMAGE_FORMAT_COMPRESSION_MODE_1; @@ -1210,11 +1216,26 @@ PiSPCameraData::platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const StreamConfiguration *rawStream = rawStreams[0].cfg; BayerFormat bayer = BayerFormat::fromPixelFormat(rawStream->pixelFormat); + const V4L2VideoDevice *cfe = cfe_[Cfe::Output0].dev(); + unsigned int sensorBits = + MediaBusFormatInfo::info(rpiConfig->sensorFormat_.code).bitsPerPixel; + + /* + * Unpacked 10 to 14-bit samples at the sensor bit depth are + * aligned to the LSB of 16-bit words, which the Frontend output + * produces with a right shift, if the CFE offers the format. + */ + bool lsbAligned = bayer.packing == BayerFormat::Packing::None && + bayer.bitDepth == sensorBits && + sensorBits > 8 && sensorBits < 16 && + cfe->toV4L2PixelFormat(bayer.toPixelFormat()).isValid(); + /* - * We cannot output CSI2 packed or non 16-bit output from the frontend, - * so signal the output as unpacked 16-bits in these cases. + * We cannot output CSI2 packed or other non 16-bit output from the + * frontend, so signal the output as unpacked 16-bits in these cases. */ - if (bayer.packing == BayerFormat::Packing::CSI2 || bayer.bitDepth != 16) { + if (!lsbAligned && + (bayer.packing == BayerFormat::Packing::CSI2 || bayer.bitDepth != 16)) { bayer.packing = (bayer.packing == BayerFormat::Packing::CSI2) ? BayerFormat::Packing::PISP1 : BayerFormat::Packing::None; bayer.bitDepth = 16;