From 6eb567e645f9bc95613ec0bc806fd1c3f6a8a02d Mon Sep 17 00:00:00 2001 From: Naushir Patuck Date: Thu, 4 May 2023 09:15:27 +0100 Subject: ipa: rpi: Add support for Raspberry Pi 5 Add the Raspberry Pi 5 ISP (PiSP) IPA to libcamera. To include this IPA in the build, set the following meson option: meson configure -Dipas=rpi/pisp Signed-off-by: Naushir Patuck Reviewed-by: David Plowman --- src/ipa/rpi/pisp/pisp.cpp | 997 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 997 insertions(+) create mode 100644 src/ipa/rpi/pisp/pisp.cpp (limited to 'src/ipa/rpi/pisp/pisp.cpp') diff --git a/src/ipa/rpi/pisp/pisp.cpp b/src/ipa/rpi/pisp/pisp.cpp new file mode 100644 index 00000000..98d7e393 --- /dev/null +++ b/src/ipa/rpi/pisp/pisp.cpp @@ -0,0 +1,997 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2023, Raspberry Pi Ltd + * + * pisp.cpp - Raspberry Pi PiSP IPA + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "libpisp/backend/backend.hpp" +#include "libpisp/frontend/frontend.hpp" + +#include "common/ipa_base.h" +#include "controller/af_status.h" +#include "controller/agc_algorithm.h" +#include "controller/alsc_status.h" +#include "controller/awb_status.h" +#include "controller/black_level_status.h" +#include "controller/cac_status.h" +#include "controller/ccm_status.h" +#include "controller/contrast_status.h" +#include "controller/denoise_algorithm.h" +#include "controller/denoise_status.h" +#include "controller/dpc_status.h" +#include "controller/geq_status.h" +#include "controller/hdr_status.h" +#include "controller/lux_status.h" +#include "controller/noise_status.h" +#include "controller/pwl.h" +#include "controller/saturation_status.h" +#include "controller/sharpen_status.h" +#include "controller/stitch_status.h" +#include "controller/tonemap_status.h" + +using namespace std::literals::chrono_literals; + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPARPI) + +namespace { + +constexpr unsigned int NumLscCells = PISP_BE_LSC_GRID_SIZE; +constexpr unsigned int NumLscVertexes = NumLscCells + 1; + +inline int32_t clampField(double value, std::size_t fieldBits, std::size_t fracBits = 0, + bool isSigned = false, const char *desc = nullptr) +{ + ASSERT(fracBits <= fieldBits && fieldBits <= 32); + + int min = -(isSigned << (fieldBits - 1)); + int max = (1 << (fieldBits - isSigned)) - 1; + int32_t val = + std::clamp(std::round(value * (1 << fracBits)), min, max); + + if (desc && val / (1 << fracBits) != value) + LOG(IPARPI, Warning) + << desc << " rounded/clamped to " << val / (1 << fracBits); + + return val; +} + +int generateLut(const RPiController::Pwl &pwl, uint32_t *lut, std::size_t lutSize, + unsigned int SlopeBits = 14, unsigned int PosBits = 16) +{ + if (pwl.empty()) + return -EINVAL; + + int lastY = 0; + for (unsigned int i = 0; i < lutSize; i++) { + int x, y; + if (i < 32) + x = i * 512; + else if (i < 48) + x = (i - 32) * 1024 + 16384; + else + x = std::min(65535u, (i - 48) * 2048 + 32768); + + y = pwl.eval(x); + if (y < 0 || (i && y < lastY)) { + LOG(IPARPI, Error) + << "Malformed PWL for Gamma, disabling!"; + return -1; + } + + if (i) { + unsigned int slope = y - lastY; + if (slope >= (1u << SlopeBits)) { + slope = (1u << SlopeBits) - 1; + LOG(IPARPI, Info) + << ("Maximum Gamma slope exceeded, adjusting!"); + y = lastY + slope; + } + lut[i - 1] |= slope << PosBits; + } + + lut[i] = y; + lastY = y; + } + + return 0; +} + +void packLscLut(uint32_t packed[NumLscVertexes][NumLscVertexes], + double const rgb[3][NumLscVertexes][NumLscVertexes]) +{ + for (unsigned int y = 0; y < NumLscVertexes; ++y) { + for (unsigned int x = 0; x < NumLscVertexes; ++x) { + /* Jointly encode RGB gains in one of 4 ranges: [0.5:1.5), [0:2), [0:4), [0:8) */ + double lo = std::min({ rgb[0][y][x], rgb[1][y][x], rgb[2][y][x] }); + double hi = std::max({ rgb[0][y][x], rgb[1][y][x], rgb[2][y][x] }); + uint32_t range; + double scale, offset; + if (lo >= 0.5 && hi < 1.5) { + range = 0; + scale = 1024.0; + offset = -511.5; + } else if (hi < 2.0) { + range = 1; + scale = 512.0; + offset = 0.5; + } else if (hi < 4.0) { + range = 2; + scale = 256.0; + offset = 0.5; + } else { + range = 3; + scale = 128.0; + offset = 0.5; + } + int r = clampField(offset + scale * rgb[0][y][x], 10); + int g = clampField(offset + scale * rgb[1][y][x], 10); + int b = clampField(offset + scale * rgb[2][y][x], 10); + packed[y][x] = (range << 30) | (b << 20) | (g << 10) | r; + } + } +} + +/* + * Resamples a srcW x srcH table with central sampling to destW x destH with + * corner sampling. + */ +void resampleTable(double *dest, int destW, int destH, double const *src, + int srcW, int srcH) +{ + /* + * Precalculate and cache the x sampling locations and phases to + * save recomputing them on every row. + */ + ASSERT(destW > 1 && destH > 1 && destW <= 64); + int xLo[64], xHi[64]; + double xf[64]; + double x = -0.5, xInc = srcW / (destW - 1); + for (int i = 0; i < destW; i++, x += xInc) { + xLo[i] = floor(x); + xf[i] = x - xLo[i]; + xHi[i] = xLo[i] < (srcW - 1) ? (xLo[i] + 1) : (srcW - 1); + xLo[i] = xLo[i] > 0 ? xLo[i] : 0; + } + + /* Now march over the output table generating the new values. */ + double y = -0.5, yInc = srcH / (destH - 1); + for (int j = 0; j < destH; j++, y += yInc) { + int yLo = floor(y); + double yf = y - yLo; + int yHi = yLo < (srcH - 1) ? (yLo + 1) : (srcH - 1); + yLo = yLo > 0 ? yLo : 0; + double const *rowAbove = src + yLo * srcW; + double const *rowBelow = src + yHi * srcW; + for (int i = 0; i < destW; i++) { + double above = rowAbove[xLo[i]] * (1 - xf[i]) + + rowAbove[xHi[i]] * xf[i]; + double below = rowBelow[xLo[i]] * (1 - xf[i]) + + rowBelow[xHi[i]] * xf[i]; + *(dest++) = above * (1 - yf) + below * yf; + } + } +} + +} /* namespace */ + +using ::libpisp::BackEnd; +using ::libpisp::FrontEnd; + +namespace ipa::RPi { + +class IpaPiSP final : public IpaBase +{ +public: + IpaPiSP() + : IpaBase(), fe_(nullptr), be_(nullptr) + { + } + + ~IpaPiSP() + { + if (fe_) + munmap(fe_, sizeof(FrontEnd)); + if (be_) + munmap(be_, sizeof(BackEnd)); + } + +private: + int32_t platformInit(const InitParams ¶ms, InitResult *result) override; + int32_t platformStart(const ControlList &controls, StartResult *result) override; + int32_t platformConfigure(const ConfigParams ¶ms, ConfigResult *result) override; + + void platformPrepareIsp(const PrepareParams ¶ms, + RPiController::Metadata &rpiMetadata) override; + RPiController::StatisticsPtr platformProcessStats(Span mem) override; + + void handleControls(const ControlList &controls) override; + + void applyWBG(const AwbStatus *awbStatus, const AgcPrepareStatus *agcStatus, + pisp_be_global_config &global); + void applyCAC(const CacStatus *cacStatus, pisp_be_global_config &global); + void applyContrast(const ContrastStatus *contrastStatus, + pisp_be_global_config &global); + void applyCCM(const CcmStatus *ccmStatus, pisp_be_global_config &global); + void applyBlackLevel(const BlackLevelStatus *blackLevelStatus, + pisp_be_global_config &global); + void applyLensShading(const AlscStatus *alscStatus, + pisp_be_global_config &global); + void applyDPC(const DpcStatus *dpcStatus, pisp_be_global_config &global); + void applySdn(const SdnStatus *sdnStatus, pisp_be_global_config &global); + void applyTdn(const TdnStatus *tdnStatus, const DeviceStatus *deviceStatus, + pisp_be_global_config &global); + void applyCdn(const CdnStatus *cdnStatus, pisp_be_global_config &global); + void applyGeq(const GeqStatus *geqStatus, pisp_be_global_config &global); + void applySaturation(const SaturationStatus *geqStatus, + pisp_be_global_config &global); + void applySharpen(const SharpenStatus *sharpenStatus, + pisp_be_global_config &global); + bool applyStitch(const StitchStatus *stitchStatus, const DeviceStatus *deviceStatus, + const AgcStatus *agcStatus, pisp_be_global_config &global); + void applyTonemap(const TonemapStatus *tonemapStatus, + pisp_be_global_config &global); + void applyFocusStats(const NoiseStatus *noiseStatus); + void applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls); + + void setDefaultConfig(); + void setStatsAndDebin(); + void setHistogramWeights(); + + /* Frontend/Backend objects passed in from the pipeline handler. */ + SharedFD feFD_; + SharedFD beFD_; + FrontEnd *fe_; + BackEnd *be_; + + /* TDN/HDR runtime need the following state. */ + bool tdnReset_; + utils::Duration lastExposure_; + std::map lastStitchExposures_; + HdrStatus lastStitchHdrStatus_; +}; + +int32_t IpaPiSP::platformInit(const InitParams ¶ms, + [[maybe_unused]] InitResult *result) +{ + const std::string &target = controller_.getTarget(); + if (target != "pisp") { + LOG(IPARPI, Error) + << "Tuning data file target returned \"" << target << "\"" + << ", expected \"pisp\""; + return -EINVAL; + } + + /* Acquire the Frontend and Backend objects. */ + feFD_ = std::move(params.fe); + beFD_ = std::move(params.be); + + if (!feFD_.isValid() || !beFD_.isValid()) { + LOG(IPARPI, Error) << "Invalid FE/BE handles!"; + return -ENODEV; + } + + fe_ = static_cast(mmap(nullptr, sizeof(FrontEnd), + PROT_READ | PROT_WRITE, MAP_SHARED, + feFD_.get(), 0)); + be_ = static_cast(mmap(nullptr, sizeof(BackEnd), + PROT_READ | PROT_WRITE, MAP_SHARED, + beFD_.get(), 0)); + + if (!fe_ || !be_) { + LOG(IPARPI, Error) << "Unable to map FE/BE handles!"; + return -ENODEV; + } + + setDefaultConfig(); + + return 0; +} + +int32_t IpaPiSP::platformStart([[maybe_unused]] const ControlList &controls, + [[maybe_unused]] StartResult *result) +{ + tdnReset_ = true; + + /* Cause the stitch block to be reset correctly. */ + lastStitchHdrStatus_ = HdrStatus(); + + return 0; +} + +int32_t IpaPiSP::platformConfigure([[maybe_unused]] const ConfigParams ¶ms, + [[maybe_unused]] ConfigResult *result) +{ + setStatsAndDebin(); + return 0; +} + +void IpaPiSP::platformPrepareIsp([[maybe_unused]] const PrepareParams ¶ms, + RPiController::Metadata &rpiMetadata) +{ + std::scoped_lock l(rpiMetadata); + + pisp_be_global_config global; + be_->GetGlobal(global); + + global.bayer_enables &= ~(PISP_BE_BAYER_ENABLE_BLC + PISP_BE_BAYER_ENABLE_WBG + + PISP_BE_BAYER_ENABLE_GEQ + PISP_BE_BAYER_ENABLE_LSC + + PISP_BE_BAYER_ENABLE_SDN + PISP_BE_BAYER_ENABLE_CDN + + PISP_BE_BAYER_ENABLE_TDN_OUTPUT + PISP_BE_BAYER_ENABLE_TDN_INPUT + + PISP_BE_BAYER_ENABLE_STITCH_INPUT + PISP_BE_BAYER_ENABLE_STITCH_OUTPUT + + PISP_BE_BAYER_ENABLE_STITCH + PISP_BE_BAYER_ENABLE_TONEMAP); + global.rgb_enables &= ~(PISP_BE_RGB_ENABLE_GAMMA + PISP_BE_RGB_ENABLE_CCM + + PISP_BE_RGB_ENABLE_YCBCR + PISP_BE_RGB_ENABLE_YCBCR_INVERSE + + PISP_BE_RGB_ENABLE_SHARPEN + PISP_BE_RGB_ENABLE_SAT_CONTROL); + + NoiseStatus *noiseStatus = rpiMetadata.getLocked("noise.status"); + AgcPrepareStatus *agcPrepareStatus = rpiMetadata.getLocked("agc.prepare_status"); + + { + /* All Frontend config goes first, we do not want to hold the FE lock for long! */ + std::scoped_lock lf(*fe_); + + if (noiseStatus) + applyFocusStats(noiseStatus); + + BlackLevelStatus *blackLevelStatus = + rpiMetadata.getLocked("black_level.status"); + if (blackLevelStatus) + applyBlackLevel(blackLevelStatus, global); + + AwbStatus *awbStatus = rpiMetadata.getLocked("awb.status"); + if (awbStatus) + applyWBG(awbStatus, agcPrepareStatus, global); + } + + CacStatus *cacStatus = rpiMetadata.getLocked("cac.status"); + if (cacStatus) + applyCAC(cacStatus, global); + + ContrastStatus *contrastStatus = + rpiMetadata.getLocked("contrast.status"); + if (contrastStatus) + applyContrast(contrastStatus, global); + + CcmStatus *ccmStatus = rpiMetadata.getLocked("ccm.status"); + if (ccmStatus) + applyCCM(ccmStatus, global); + + AlscStatus *alscStatus = rpiMetadata.getLocked("alsc.status"); + if (alscStatus) + applyLensShading(alscStatus, global); + + DpcStatus *dpcStatus = rpiMetadata.getLocked("dpc.status"); + if (dpcStatus) + applyDPC(dpcStatus, global); + + SdnStatus *sdnStatus = rpiMetadata.getLocked("sdn.status"); + if (sdnStatus) + applySdn(sdnStatus, global); + + DeviceStatus *deviceStatus = rpiMetadata.getLocked("device.status"); + TdnStatus *tdnStatus = rpiMetadata.getLocked("tdn.status"); + if (tdnStatus && deviceStatus) + applyTdn(tdnStatus, deviceStatus, global); + + CdnStatus *cdnStatus = rpiMetadata.getLocked("cdn.status"); + if (cdnStatus) + applyCdn(cdnStatus, global); + + GeqStatus *geqStatus = rpiMetadata.getLocked("geq.status"); + if (geqStatus) + applyGeq(geqStatus, global); + + SaturationStatus *saturationStatus = + rpiMetadata.getLocked("saturation.status"); + if (saturationStatus) + applySaturation(saturationStatus, global); + + SharpenStatus *sharpenStatus = rpiMetadata.getLocked("sharpen.status"); + if (sharpenStatus) + applySharpen(sharpenStatus, global); + + StitchStatus *stitchStatus = rpiMetadata.getLocked("stitch.status"); + if (stitchStatus) { + /* + * Note that it's the *delayed* AGC status that contains the HDR mode/channel + * info that pertains to this frame! + */ + AgcStatus *agcStatus = rpiMetadata.getLocked("agc.delayed_status"); + /* prepareIsp() will fetch this value. Maybe pass it back differently? */ + stitchSwapBuffers_ = applyStitch(stitchStatus, deviceStatus, agcStatus, global); + } else + lastStitchHdrStatus_ = HdrStatus(); + + TonemapStatus *tonemapStatus = rpiMetadata.getLocked("tonemap.status"); + if (tonemapStatus) + applyTonemap(tonemapStatus, global); + + be_->SetGlobal(global); + + /* Save this for TDN and HDR on the next frame. */ + lastExposure_ = deviceStatus->shutterSpeed * deviceStatus->analogueGain; + + /* Lens control */ + const AfStatus *afStatus = rpiMetadata.getLocked("af.status"); + if (afStatus) { + ControlList lensctrls(lensCtrls_); + applyAF(afStatus, lensctrls); + if (!lensctrls.empty()) + setLensControls.emit(lensctrls); + } +} + +RPiController::StatisticsPtr IpaPiSP::platformProcessStats(Span mem) +{ + using namespace RPiController; + + const pisp_statistics *stats = reinterpret_cast(mem.data()); + + unsigned int i; + StatisticsPtr statistics = + std::make_unique(Statistics::AgcStatsPos::PostWb, + Statistics::ColourStatsPos::PreLsc); + + /* RGB histograms are not used, so do not populate them. */ + statistics->yHist = RPiController::Histogram(stats->agc.histogram, + PISP_AGC_STATS_NUM_BINS); + + statistics->awbRegions.init({ PISP_AWB_STATS_SIZE, PISP_AWB_STATS_SIZE }); + for (i = 0; i < statistics->awbRegions.numRegions(); i++) + statistics->awbRegions.set(i, { { stats->awb.zones[i].R_sum, + stats->awb.zones[i].G_sum, + stats->awb.zones[i].B_sum }, + stats->awb.zones[i].counted, 0 }); + + /* AGC region sums only get collected on floating zones. */ + statistics->agcRegions.init({ 0, 0 }, PISP_FLOATING_STATS_NUM_ZONES); + for (i = 0; i < statistics->agcRegions.numRegions(); i++) + statistics->agcRegions.setFloating(i, + { { 0, 0, 0, stats->agc.floating[i].Y_sum }, + stats->agc.floating[i].counted, 0 }); + + statistics->focusRegions.init({ PISP_CDAF_STATS_SIZE, PISP_CDAF_STATS_SIZE }); + for (i = 0; i < statistics->focusRegions.numRegions(); i++) + statistics->focusRegions.set(i, { stats->cdaf.foms[i] >> 20, 0, 0 }); + + return statistics; +} + +void IpaPiSP::handleControls(const ControlList &controls) +{ + for (auto const &ctrl : controls) { + switch (ctrl.first) { + case controls::HDR_MODE: + case controls::AE_METERING_MODE: + setHistogramWeights(); + break; + + case controls::NOISE_REDUCTION_MODE: { + RPiController::DenoiseAlgorithm *denoise = dynamic_cast( + controller_.getAlgorithm("denoise")); + + if (!denoise) { + LOG(IPARPI, Warning) + << "Could not set NOISE_REDUCTION_MODE - no Denoise algorithm"; + return; + } + + if (ctrl.second.get() == controls::draft::NoiseReductionModeOff) + denoise->setMode(RPiController::DenoiseMode::Off); + else + denoise->setMode(RPiController::DenoiseMode::ColourHighQuality); + + break; + } + } + } +} + +void IpaPiSP::applyWBG(const AwbStatus *awbStatus, const AgcPrepareStatus *agcPrepareStatus, + pisp_be_global_config &global) +{ + pisp_wbg_config wbg; + pisp_fe_rgby_config rgby = {}; + double dg = agcPrepareStatus ? agcPrepareStatus->digitalGain : 1.0; + + wbg.gain_r = clampField(dg * awbStatus->gainR, 14, 10); + wbg.gain_g = clampField(dg * awbStatus->gainG, 14, 10); + wbg.gain_b = clampField(dg * awbStatus->gainB, 14, 10); + + /* + * The YCbCr conversion block should contain the appropriate YCbCr + * matrix. We should not rely on the CSC0 block as that might be + * programmed for RGB outputs. + */ + pisp_be_ccm_config csc; + be_->GetYcbcr(csc); + + /* The CSC coefficients already have the << 10 scaling applied. */ + rgby.gain_r = clampField(csc.coeffs[0] * awbStatus->gainR, 14); + rgby.gain_g = clampField(csc.coeffs[1] * awbStatus->gainG, 14); + rgby.gain_b = clampField(csc.coeffs[2] * awbStatus->gainB, 14); + + LOG(IPARPI, Debug) << "Applying WB R: " << awbStatus->gainR << " B: " + << awbStatus->gainB; + + be_->SetWbg(wbg); + fe_->SetRGBY(rgby); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_WBG; +} + +void IpaPiSP::applyContrast(const ContrastStatus *contrastStatus, + pisp_be_global_config &global) +{ + pisp_be_gamma_config gamma; + + if (!generateLut(contrastStatus->gammaCurve, gamma.lut, PISP_BE_GAMMA_LUT_SIZE)) { + be_->SetGamma(gamma); + global.rgb_enables |= PISP_BE_RGB_ENABLE_GAMMA; + } +} + +void IpaPiSP::applyCCM(const CcmStatus *ccmStatus, pisp_be_global_config &global) +{ + pisp_be_ccm_config ccm = {}; + + for (unsigned int i = 0; i < 9; i++) + ccm.coeffs[i] = clampField(ccmStatus->matrix[i], 14, 10, true); + + be_->SetCcm(ccm); + global.rgb_enables |= PISP_BE_RGB_ENABLE_CCM; +} + +void IpaPiSP::applyCAC(const CacStatus *cacStatus, pisp_be_global_config &global) +{ + pisp_be_cac_config cac = {}; + + for (int x = 0; x < PISP_BE_CAC_GRID_SIZE + 1; x++) { + for (int y = 0; y < PISP_BE_CAC_GRID_SIZE + 1; y++) { + cac.lut[y][x][0][0] = clampField(cacStatus->lutRx[y * (PISP_BE_CAC_GRID_SIZE + 1) + x], 7, 5, true); + cac.lut[y][x][0][1] = clampField(cacStatus->lutRy[y * (PISP_BE_CAC_GRID_SIZE + 1) + x], 7, 5, true); + cac.lut[y][x][1][0] = clampField(cacStatus->lutBx[y * (PISP_BE_CAC_GRID_SIZE + 1) + x], 7, 5, true); + cac.lut[y][x][1][1] = clampField(cacStatus->lutBy[y * (PISP_BE_CAC_GRID_SIZE + 1) + x], 7, 5, true); + } + } + + be_->SetCac(cac); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_CAC; +} + +void IpaPiSP::applyBlackLevel(const BlackLevelStatus *blackLevelStatus, pisp_be_global_config &global) +{ + pisp_bla_config bla; + + /* Set the Frontend to adjust the black level to 4096 (in 16-bits). */ + bla.black_level_r = blackLevelStatus->blackLevelR; + bla.black_level_gr = blackLevelStatus->blackLevelG; + bla.black_level_gb = blackLevelStatus->blackLevelG; + bla.black_level_b = blackLevelStatus->blackLevelB; + bla.output_black_level = 4096; + fe_->SetBla(bla); + + /* Frontend Stats and Backend black level correction. */ + bla.black_level_r = bla.black_level_gr = + bla.black_level_gb = bla.black_level_b = 4096; + bla.output_black_level = 0; + fe_->SetBlc(bla); + be_->SetBlc(bla); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_BLC; +} + +void IpaPiSP::applyLensShading(const AlscStatus *alscStatus, + pisp_be_global_config &global) +{ + pisp_be_lsc_extra lscExtra = {}; + pisp_be_lsc_config lsc = {}; + double rgb[3][NumLscVertexes][NumLscVertexes] = {}; + + resampleTable(&rgb[0][0][0], NumLscVertexes, NumLscVertexes, + alscStatus->r.data(), NumLscCells, NumLscCells); + resampleTable(&rgb[1][0][0], NumLscVertexes, NumLscVertexes, + alscStatus->g.data(), NumLscCells, NumLscCells); + resampleTable(&rgb[2][0][0], NumLscVertexes, NumLscVertexes, + alscStatus->b.data(), NumLscCells, NumLscCells); + packLscLut(lsc.lut_packed, rgb); + be_->SetLsc(lsc, lscExtra); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_LSC; +} + +void IpaPiSP::applyDPC(const DpcStatus *dpcStatus, pisp_be_global_config &global) +{ + pisp_be_dpc_config dpc = {}; + + switch (dpcStatus->strength) { + case 0: /* "off" */ + break; + case 1: /* "normal" */ + dpc.coeff_level = 1; + dpc.coeff_range = 8; + global.bayer_enables |= PISP_BE_BAYER_ENABLE_DPC; + break; + case 2: /* "strong" */ + dpc.coeff_level = 0; + dpc.coeff_range = 0; + global.bayer_enables |= PISP_BE_BAYER_ENABLE_DPC; + break; + default: + ASSERT(0); + } + + be_->SetDpc(dpc); +} + +void IpaPiSP::applySdn(const SdnStatus *sdnStatus, pisp_be_global_config &global) +{ + pisp_be_sdn_config sdn = {}; + + sdn.black_level = 4096; + /* leakage is "amount of the original pixel we let through", thus 1 - strength */ + sdn.leakage = clampField(1.0 - sdnStatus->strength, 8, 8); + sdn.noise_constant = clampField(sdnStatus->noiseConstant, 16); + sdn.noise_slope = clampField(sdnStatus->noiseSlope, 16, 8); + sdn.noise_constant2 = clampField(sdnStatus->noiseConstant2, 16); + sdn.noise_slope2 = clampField(sdnStatus->noiseSlope2, 16, 8); + be_->SetSdn(sdn); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_SDN; +} + +void IpaPiSP::applyTdn(const TdnStatus *tdnStatus, const DeviceStatus *deviceStatus, + pisp_be_global_config &global) +{ + utils::Duration exposure = deviceStatus->shutterSpeed * deviceStatus->analogueGain; + pisp_be_tdn_config tdn = {}; + + double ratio = tdnReset_ ? 1.0 : exposure / lastExposure_; + if (ratio >= 4.0) { + /* If the exposure ratio goes above 4x, we need to reset TDN. */ + ratio = 1; + tdnReset_ = true; + } + + LOG(IPARPI, Debug) << "TDN: exposure: " << exposure + << " last: " << lastExposure_ + << " ratio: " << ratio; + + tdn.black_level = 4096; + tdn.ratio = clampField(ratio, 16, 14); + tdn.noise_constant = clampField(tdnStatus->noiseConstant, 16); + tdn.noise_slope = clampField(tdnStatus->noiseSlope, 16, 8); + tdn.threshold = clampField(tdnStatus->threshold, 16, 16); + + global.bayer_enables |= PISP_BE_BAYER_ENABLE_TDN + PISP_BE_BAYER_ENABLE_TDN_OUTPUT; + + /* Only enable the TDN Input after a state reset. */ + if (!tdnReset_) { + global.bayer_enables |= PISP_BE_BAYER_ENABLE_TDN_INPUT; + tdn.reset = 0; + } else + tdn.reset = 1; + + be_->SetTdn(tdn); + tdnReset_ = false; +} + +void IpaPiSP::applyCdn(const CdnStatus *cdnStatus, pisp_be_global_config &global) +{ + pisp_be_cdn_config cdn = {}; + + cdn.thresh = clampField(cdnStatus->threshold, 16); + cdn.iir_strength = clampField(cdnStatus->strength, 8, 8); + cdn.g_adjust = clampField(0, 8, 8); + be_->SetCdn(cdn); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_CDN; +} + +void IpaPiSP::applyGeq(const GeqStatus *geqStatus, pisp_be_global_config &global) +{ + pisp_be_geq_config geq = {}; + + geq.min = 0; + geq.max = 0xffff; + geq.offset = clampField(geqStatus->offset, 16); + geq.slope_sharper = clampField(geqStatus->slope, 10, 10); + be_->SetGeq(geq); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_GEQ; +} + +void IpaPiSP::applySaturation(const SaturationStatus *saturationStatus, + pisp_be_global_config &global) +{ + pisp_be_sat_control_config saturation; + pisp_wbg_config wbg; + + saturation.shift_r = std::min(2, saturationStatus->shiftR); + saturation.shift_g = std::min(2, saturationStatus->shiftG); + saturation.shift_b = std::min(2, saturationStatus->shiftB); + be_->SetSatControl(saturation); + + be_->GetWbg(wbg); + wbg.gain_r >>= saturationStatus->shiftR; + wbg.gain_g >>= saturationStatus->shiftG; + wbg.gain_b >>= saturationStatus->shiftB; + be_->SetWbg(wbg); + + global.rgb_enables |= PISP_BE_RGB_ENABLE_SAT_CONTROL; +} + +void IpaPiSP::applySharpen(const SharpenStatus *sharpenStatus, + pisp_be_global_config &global) +{ + /* + * This threshold scaling is to normalise the VC4 and PiSP parameter + * scales in the tuning config. + */ + static constexpr double ThresholdScaling = 0.25; + const double scaling = sharpenStatus->threshold * ThresholdScaling; + + pisp_be_sh_fc_combine_config shfc; + pisp_be_sharpen_config sharpen; + + be_->InitialiseSharpen(sharpen, shfc); + sharpen.threshold_offset0 = clampField(sharpen.threshold_offset0 * scaling, 16); + sharpen.threshold_offset1 = clampField(sharpen.threshold_offset1 * scaling, 16); + sharpen.threshold_offset2 = clampField(sharpen.threshold_offset2 * scaling, 16); + sharpen.threshold_offset3 = clampField(sharpen.threshold_offset3 * scaling, 16); + sharpen.threshold_offset4 = clampField(sharpen.threshold_offset4 * scaling, 16); + sharpen.threshold_slope0 = clampField(sharpen.threshold_slope0 * scaling, 12); + sharpen.threshold_slope1 = clampField(sharpen.threshold_slope1 * scaling, 12); + sharpen.threshold_slope2 = clampField(sharpen.threshold_slope2 * scaling, 12); + sharpen.threshold_slope3 = clampField(sharpen.threshold_slope3 * scaling, 12); + sharpen.threshold_slope4 = clampField(sharpen.threshold_slope4 * scaling, 12); + sharpen.positive_strength = clampField(sharpen.positive_strength * sharpenStatus->strength, 12); + sharpen.negative_strength = clampField(sharpen.negative_strength * sharpenStatus->strength, 12); + sharpen.positive_pre_limit = clampField(sharpen.positive_pre_limit * sharpenStatus->limit, 16); + sharpen.positive_limit = clampField(sharpen.positive_limit * sharpenStatus->limit, 16); + sharpen.negative_pre_limit = clampField(sharpen.negative_pre_limit * sharpenStatus->limit, 16); + sharpen.negative_limit = clampField(sharpen.negative_limit * sharpenStatus->limit, 16); + + be_->SetSharpen(sharpen); + /* Sharpen needs a RGB -> YCbCr and inverse transform after the block. */ + global.rgb_enables |= PISP_BE_RGB_ENABLE_YCBCR + PISP_BE_RGB_ENABLE_SHARPEN + + PISP_BE_RGB_ENABLE_YCBCR_INVERSE; +} + +bool IpaPiSP::applyStitch(const StitchStatus *stitchStatus, const DeviceStatus *deviceStatus, + const AgcStatus *agcStatus, pisp_be_global_config &global) +{ + /* + * Find out what HDR mode/channel this frame is. Normally this will be in the delayed + * HDR status (in the AGC status), though after a mode switch this will be absent and + * the information will have been stored in the hdrStatus_ field. + */ + const HdrStatus *hdrStatus = &hdrStatus_; + if (agcStatus) + hdrStatus = &agcStatus->hdr; + + bool modeChange = hdrStatus->mode != lastStitchHdrStatus_.mode; + bool channelChange = !modeChange && hdrStatus->channel != lastStitchHdrStatus_.channel; + lastStitchHdrStatus_ = *hdrStatus; + + /* Check for a change of HDR mode. That forces us to start over. */ + if (modeChange) + lastStitchExposures_.clear(); + + if (hdrStatus->channel != "short" && hdrStatus->channel != "long") { + /* The channel *must* be long or short, anything else does not make sense. */ + LOG(IPARPI, Warning) << "Stitch channel is not long or short"; + return false; + } + + /* Whatever happens, we're going to output this buffer now. */ + global.bayer_enables |= PISP_BE_BAYER_ENABLE_STITCH_OUTPUT; + + utils::Duration exposure = deviceStatus->shutterSpeed * deviceStatus->analogueGain; + lastStitchExposures_[hdrStatus->channel] = exposure; + + /* If the other channel hasn't been seen there's nothing more we can do. */ + std::string otherChannel = hdrStatus->channel == "short" ? "long" : "short"; + if (lastStitchExposures_.find(otherChannel) == lastStitchExposures_.end()) { + /* The first channel should be "short". */ + if (hdrStatus->channel != "short") + LOG(IPARPI, Warning) << "First frame is not short"; + return false; + } + + /* We have both channels, we need to enable stitching. */ + global.bayer_enables |= PISP_BE_BAYER_ENABLE_STITCH_INPUT + PISP_BE_BAYER_ENABLE_STITCH; + + utils::Duration otherExposure = lastStitchExposures_[otherChannel]; + bool phaseLong = hdrStatus->channel == "long"; + double ratio = phaseLong ? otherExposure / exposure : exposure / otherExposure; + + pisp_be_stitch_config stitch = {}; + stitch.exposure_ratio = clampField(ratio, 15, 15); + if (phaseLong) + stitch.exposure_ratio |= PISP_BE_STITCH_STREAMING_LONG; + /* These will be filled in correctly once we have implemented the HDR algorithm. */ + stitch.threshold_lo = stitchStatus->thresholdLo; + stitch.threshold_diff_power = stitchStatus->diffPower; + stitch.motion_threshold_256 = stitchStatus->motionThreshold; + be_->SetStitch(stitch); + + return channelChange; +} + +void IpaPiSP::applyTonemap(const TonemapStatus *tonemapStatus, pisp_be_global_config &global) +{ + pisp_be_tonemap_config tonemap = {}; + + tonemap.detail_constant = clampField(tonemapStatus->detailConstant, 16); + tonemap.detail_slope = clampField(tonemapStatus->detailSlope, 16, 8); + tonemap.iir_strength = clampField(tonemapStatus->iirStrength, 12, 4); + tonemap.strength = clampField(tonemapStatus->strength, 12, 8); + + if (!generateLut(tonemapStatus->tonemap, tonemap.lut, PISP_BE_TONEMAP_LUT_SIZE)) { + be_->SetTonemap(tonemap); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_TONEMAP; + } +} + +void IpaPiSP::applyFocusStats(const NoiseStatus *noiseStatus) +{ + pisp_fe_cdaf_stats_config cdaf; + fe_->GetCdafStats(cdaf); + + cdaf.noise_constant = noiseStatus->noiseConstant; + cdaf.noise_slope = noiseStatus->noiseSlope; + fe_->SetCdafStats(cdaf); +} + +void IpaPiSP::applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls) +{ + if (afStatus->lensSetting) { + ControlValue v(afStatus->lensSetting.value()); + lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, v); + } +} + +void IpaPiSP::setDefaultConfig() +{ + std::scoped_lock l(*fe_); + + pisp_be_global_config beGlobal; + + be_->GetGlobal(beGlobal); + beGlobal.bayer_enables |= PISP_BE_BAYER_ENABLE_DEMOSAIC; + beGlobal.rgb_enables |= PISP_BE_RGB_ENABLE_FALSE_COLOUR; + be_->SetGlobal(beGlobal); + + pisp_fe_rgby_config rgby = {}; + rgby.gain_r = rgby.gain_b = clampField(1.0, 14, 10); + rgby.gain_g = clampField(1.0, 14, 10); + fe_->SetRGBY(rgby); + + pisp_fe_global_config feGlobal; + fe_->GetGlobal(feGlobal); + feGlobal.enables |= PISP_FE_ENABLE_BLA + PISP_FE_ENABLE_BLC + PISP_FE_ENABLE_RGBY; + fe_->SetGlobal(feGlobal); +} + +void IpaPiSP::setStatsAndDebin() +{ + pisp_fe_crop_config crop{ 0, 0, mode_.width, mode_.height }; + + pisp_fe_awb_stats_config awb = {}; + awb.r_lo = awb.g_lo = awb.b_lo = 0; + awb.r_hi = awb.g_hi = awb.b_hi = 65535 * 0.98; + + pisp_fe_cdaf_stats_config cdaf = {}; + cdaf.mode = (1 << 4) + (1 << 2) + 1; /* Gr / Gb count with weights of (1, 1) */ + + { + std::scoped_lock l(*fe_); + pisp_fe_global_config feGlobal; + fe_->GetGlobal(feGlobal); + feGlobal.enables |= PISP_FE_ENABLE_AWB_STATS + PISP_FE_ENABLE_AGC_STATS + + PISP_FE_ENABLE_CDAF_STATS; + + fe_->SetGlobal(feGlobal); + fe_->SetStatsCrop(crop); + fe_->SetAwbStats(awb); + fe_->SetCdafStats(cdaf); + } + + /* + * Apply the correct AGC region weights to the Frontend. Need to do this + * out of the Frontend scoped lock. + */ + setHistogramWeights(); + + pisp_be_global_config beGlobal; + be_->GetGlobal(beGlobal); + + if (mode_.binX > 1 || mode_.binY > 1) { + pisp_be_debin_config debin; + + be_->GetDebin(debin); + debin.h_enable = (mode_.binX > 1); + debin.v_enable = (mode_.binY > 1); + be_->SetDebin(debin); + beGlobal.bayer_enables |= PISP_BE_BAYER_ENABLE_DEBIN; + } else + beGlobal.bayer_enables &= ~PISP_BE_BAYER_ENABLE_DEBIN; + + be_->SetGlobal(beGlobal); +} + +void IpaPiSP::setHistogramWeights() +{ + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (!agc) + return; + + const std::vector &weights = agc->getWeights(); + + pisp_fe_agc_stats_config config; + memset(&config, 0, sizeof(config)); + + /* + * The AGC software gives us a 15x15 table of weights which we + * map onto 16x16 in the hardware, ensuring the rightmost column + * and bottom row all have zero weight. We align everything to + * the native 2x2 Bayer pixel blocks. + */ + const Size &size = controller_.getHardwareConfig().agcZoneWeights; + int width = (mode_.width / size.width) & ~1; + int height = (mode_.height / size.height) & ~1; + config.offset_x = ((mode_.width - size.width * width) / 2) & ~1; + config.offset_y = ((mode_.height - size.height * height) / 2) & ~1; + config.size_x = width; + config.size_y = height; + + unsigned int idx = 0; + for (unsigned int row = 0; row < size.height; row++) { + unsigned int col = 0; + for (; col < size.width / 2; col++) { + int wt0 = clampField(weights[idx++], 4, 0, false, "agc weights"); + int wt1 = clampField(weights[idx++], 4, 0, false, "agc weights"); + config.weights[row * 8 + col] = (wt1 << 4) | wt0; + } + if (size.width & 1) + config.weights[row * 8 + col] = + clampField(weights[idx++], 4, 0, false, "agc weights"); + } + + std::scoped_lock l(*fe_); + fe_->SetAgcStats(config); +} + +} /* namespace ipa::RPi */ + +/* + * External IPA module interface + */ +extern "C" { +const IPAModuleInfo ipaModuleInfo = { + IPA_MODULE_API_VERSION, + 1, + "PipelineHandlerPiSP", + "rpi/pisp", +}; + +IPAInterface *ipaCreate() +{ + return new ipa::RPi::IpaPiSP(); +} + +} /* extern "C" */ + +} /* namespace libcamera */ -- cgit v1.2.1