root/drivers/staging/media/atomisp/pci/sh_css_param_shading.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Support for Intel Camera Imaging ISP subsystem.
 * Copyright (c) 2015, Intel Corporation.
 */

#include <linux/math.h>
#include <linux/slab.h>

#include <math_support.h>
#include "sh_css_param_shading.h"
#include "ia_css_shading.h"
#include "assert_support.h"
#include "sh_css_defs.h"
#include "sh_css_internal.h"
#include "ia_css_debug.h"
#include "ia_css_pipe_binarydesc.h"

#include "sh_css_hrt.h"

#include "platform_support.h"

/* Bilinear interpolation on shading tables:
 * For each target point T, we calculate the 4 surrounding source points:
 * ul (upper left), ur (upper right), ll (lower left) and lr (lower right).
 * We then calculate the distances from the T to the source points: x0, x1,
 * y0 and y1.
 * We then calculate the value of T:
 *   dx0*dy0*Slr + dx0*dy1*Sur + dx1*dy0*Sll + dx1*dy1*Sul.
 * We choose a grid size of 1x1 which means:
 *   dx1 = 1-dx0
 *   dy1 = 1-dy0
 *
 *   Sul dx0         dx1      Sur
 *    .<----->|<------------->.
 *    ^
 * dy0|
 *    v        T
 *    -        .
 *    ^
 *    |
 * dy1|
 *    v
 *    .                        .
 *   Sll                      Slr
 *
 * Padding:
 * The area that the ISP operates on can include padding both on the left
 * and the right. We need to padd the shading table such that the shading
 * values end up on the correct pixel values. This means we must padd the
 * shading table to match the ISP padding.
 * We can have 5 cases:
 * 1. All 4 points fall in the left padding.
 * 2. The left 2 points fall in the left padding.
 * 3. All 4 points fall in the cropped (target) region.
 * 4. The right 2 points fall in the right padding.
 * 5. All 4 points fall in the right padding.
 * Cases 1 and 5 are easy to handle: we simply use the
 * value 1 in the shading table.
 * Cases 2 and 4 require interpolation that takes into
 * account how far into the padding area the pixels
 * fall. We extrapolate the shading table into the
 * padded area and then interpolate.
 */
static void
crop_and_interpolate(unsigned int cropped_width,
                     unsigned int cropped_height,
                     unsigned int left_padding,
                     int right_padding,
                     int top_padding,
                     const struct ia_css_shading_table *in_table,
                     struct ia_css_shading_table *out_table,
                     enum ia_css_sc_color color)
{
        unsigned int i, j,
                 sensor_width,
                 sensor_height,
                 table_width,
                 table_height,
                 table_cell_h,
                 out_cell_size,
                 in_cell_size,
                 out_start_row,
                 padded_width;
        int out_start_col, /* can be negative to indicate padded space */
            table_cell_w;
        unsigned short *in_ptr,
                 *out_ptr;

        assert(in_table);
        assert(out_table);

        sensor_width  = in_table->sensor_width;
        sensor_height = in_table->sensor_height;
        table_width   = in_table->width;
        table_height  = in_table->height;
        in_ptr = in_table->data[color];
        out_ptr = out_table->data[color];

        padded_width = cropped_width + left_padding + right_padding;
        out_cell_size = CEIL_DIV(padded_width, out_table->width - 1);
        in_cell_size  = CEIL_DIV(sensor_width, table_width - 1);

        out_start_col = ((int)sensor_width - (int)cropped_width) / 2 - left_padding;
        out_start_row = ((int)sensor_height - (int)cropped_height) / 2 - top_padding;
        table_cell_w = (int)((table_width - 1) * in_cell_size);
        table_cell_h = (table_height - 1) * in_cell_size;

        for (i = 0; i < out_table->height; i++) {
                int ty, src_y0, src_y1;
                unsigned int sy0, sy1, dy0, dy1, divy;

                /*
                 * calculate target point and make sure it falls within
                 * the table
                 */
                ty = out_start_row + i * out_cell_size;

                /* calculate closest source points in shading table and
                   make sure they fall within the table */
                src_y0 = ty / (int)in_cell_size;
                if (in_cell_size < out_cell_size)
                        src_y1 = (ty + out_cell_size) / in_cell_size;
                else
                        src_y1 = src_y0 + 1;
                src_y0 = clamp(src_y0, 0, (int)table_height - 1);
                src_y1 = clamp(src_y1, 0, (int)table_height - 1);
                ty = min(clamp(ty, 0, (int)sensor_height - 1),
                         (int)table_cell_h);

                /* calculate closest source points for distance computation */
                sy0 = min(src_y0 * in_cell_size, sensor_height - 1);
                sy1 = min(src_y1 * in_cell_size, sensor_height - 1);
                /* calculate distance between source and target pixels */
                dy0 = ty - sy0;
                dy1 = sy1 - ty;
                divy = sy1 - sy0;
                if (divy == 0) {
                        dy0 = 1;
                        divy = 1;
                }

                for (j = 0; j < out_table->width; j++, out_ptr++) {
                        int tx, src_x0, src_x1;
                        unsigned int sx0, sx1, dx0, dx1, divx;
                        unsigned short s_ul, s_ur, s_ll, s_lr;

                        /* calculate target point */
                        tx = out_start_col + j * out_cell_size;
                        /* calculate closest source points. */
                        src_x0 = tx / (int)in_cell_size;
                        if (in_cell_size < out_cell_size) {
                                src_x1 = (tx + out_cell_size) /
                                         (int)in_cell_size;
                        } else {
                                src_x1 = src_x0 + 1;
                        }
                        /* if src points fall in padding, select closest ones.*/
                        src_x0 = clamp(src_x0, 0, (int)table_width - 1);
                        src_x1 = clamp(src_x1, 0, (int)table_width - 1);
                        tx = min(clamp(tx, 0, (int)sensor_width - 1),
                                 (int)table_cell_w);
                        /*
                         * calculate closest source points for distance
                         * computation
                         */
                        sx0 = min(src_x0 * in_cell_size, sensor_width - 1);
                        sx1 = min(src_x1 * in_cell_size, sensor_width - 1);
                        /*
                         * calculate distances between source and target
                         * pixels
                         */
                        dx0 = tx - sx0;
                        dx1 = sx1 - tx;
                        divx = sx1 - sx0;
                        /* if we're at the edge, we just use the closest
                         * point still in the grid. We make up for the divider
                         * in this case by setting the distance to
                         * out_cell_size, since it's actually 0.
                         */
                        if (divx == 0) {
                                dx0 = 1;
                                divx = 1;
                        }

                        /* get source pixel values */
                        s_ul = in_ptr[(table_width * src_y0) + src_x0];
                        s_ur = in_ptr[(table_width * src_y0) + src_x1];
                        s_ll = in_ptr[(table_width * src_y1) + src_x0];
                        s_lr = in_ptr[(table_width * src_y1) + src_x1];

                        *out_ptr = (unsigned short)((dx0 * dy0 * s_lr + dx0 * dy1 * s_ur + dx1 * dy0 *
                                                     s_ll + dx1 * dy1 * s_ul) /
                                                    (divx * divy));
                }
        }
}

void
sh_css_params_shading_id_table_generate(
    struct ia_css_shading_table **target_table,
    unsigned int table_width,
    unsigned int table_height)
{
        /* initialize table with ones, shift becomes zero */
        unsigned int i, j;
        struct ia_css_shading_table *result;

        assert(target_table);

        result = ia_css_shading_table_alloc(table_width, table_height);
        if (!result) {
                *target_table = NULL;
                return;
        }

        for (i = 0; i < IA_CSS_SC_NUM_COLORS; i++) {
                for (j = 0; j < table_height * table_width; j++)
                        result->data[i][j] = 1;
        }
        result->fraction_bits = 0;
        *target_table = result;
}

void
prepare_shading_table(const struct ia_css_shading_table *in_table,
                      unsigned int sensor_binning,
                      struct ia_css_shading_table **target_table,
                      const struct ia_css_binary *binary,
                      unsigned int bds_factor)
{
        unsigned int input_width, input_height, table_width, table_height, i;
        unsigned int left_padding, top_padding, left_cropping;
        struct ia_css_shading_table *result;
        struct u32_fract bds;
        int right_padding;

        assert(target_table);
        assert(binary);

        if (!in_table) {
                sh_css_params_shading_id_table_generate(target_table,
                                                        binary->sctbl_width_per_color,
                                                        binary->sctbl_height);
                return;
        }

        /*
         * We use the ISP input resolution for the shading table because
         * shading correction is performed in the bayer domain (before bayer
         * down scaling).
         */
        input_height  = binary->in_frame_info.res.height;
        input_width   = binary->in_frame_info.res.width;
        left_padding  = binary->left_padding;
        left_cropping = (binary->info->sp.pipeline.left_cropping == 0) ?
                        binary->dvs_envelope.width : 2 * ISP_VEC_NELEMS;

        sh_css_bds_factor_get_fract(bds_factor, &bds);

        left_padding  = (left_padding + binary->info->sp.pipeline.left_cropping) *
                        bds.numerator / bds.denominator -
                        binary->info->sp.pipeline.left_cropping;
        right_padding = (binary->internal_frame_info.res.width -
                         binary->effective_in_frame_res.width * bds.denominator /
                         bds.numerator - left_cropping) * bds.numerator / bds.denominator;
        top_padding = binary->info->sp.pipeline.top_cropping * bds.numerator /
                      bds.denominator -
                      binary->info->sp.pipeline.top_cropping;

        /*
         * We take into account the binning done by the sensor. We do this
         * by cropping the non-binned part of the shading table and then
         * increasing the size of a grid cell with this same binning factor.
         */
        input_width  <<= sensor_binning;
        input_height <<= sensor_binning;
        /*
         * We also scale the padding by the same binning factor. This will
         * make it much easier later on to calculate the padding of the
         * shading table.
         */
        left_padding  <<= sensor_binning;
        right_padding <<= sensor_binning;
        top_padding   <<= sensor_binning;

        /*
         * during simulation, the used resolution can exceed the sensor
         * resolution, so we clip it.
         */
        input_width  = min(input_width,  in_table->sensor_width);
        input_height = min(input_height, in_table->sensor_height);

        /* This prepare_shading_table() function is called only in legacy API (not in new API).
           Then, the legacy shading table width and height should be used. */
        table_width  = binary->sctbl_width_per_color;
        table_height = binary->sctbl_height;

        result = ia_css_shading_table_alloc(table_width, table_height);
        if (!result) {
                *target_table = NULL;
                return;
        }
        result->sensor_width  = in_table->sensor_width;
        result->sensor_height = in_table->sensor_height;
        result->fraction_bits = in_table->fraction_bits;

        /*
         * now we crop the original shading table and then interpolate to the
         * requested resolution and decimation factor.
         */
        for (i = 0; i < IA_CSS_SC_NUM_COLORS; i++) {
                crop_and_interpolate(input_width, input_height,
                                     left_padding, right_padding, top_padding,
                                     in_table,
                                     result, i);
        }
        *target_table = result;
}

struct ia_css_shading_table *
ia_css_shading_table_alloc(
    unsigned int width,
    unsigned int height)
{
        unsigned int i;
        struct ia_css_shading_table *me;

        IA_CSS_ENTER("");

        me = kmalloc_obj(*me);
        if (!me)
                return me;

        me->width         = width;
        me->height        = height;
        me->sensor_width  = 0;
        me->sensor_height = 0;
        me->fraction_bits = 0;
        for (i = 0; i < IA_CSS_SC_NUM_COLORS; i++) {
                me->data[i] =
                    kvmalloc(width * height * sizeof(*me->data[0]),
                             GFP_KERNEL);
                if (!me->data[i]) {
                        unsigned int j;

                        for (j = 0; j < i; j++) {
                                kvfree(me->data[j]);
                                me->data[j] = NULL;
                        }
                        kfree(me);
                        return NULL;
                }
        }

        IA_CSS_LEAVE("");
        return me;
}

void
ia_css_shading_table_free(struct ia_css_shading_table *table)
{
        unsigned int i;

        if (!table)
                return;

        /*
         * We only output logging when the table is not NULL, otherwise
         * logs will give the impression that a table was freed.
         */
        IA_CSS_ENTER("");

        for (i = 0; i < IA_CSS_SC_NUM_COLORS; i++) {
                if (table->data[i]) {
                        kvfree(table->data[i]);
                        table->data[i] = NULL;
                }
        }
        kfree(table);

        IA_CSS_LEAVE("");
}