root/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Driver for STM32 Digital Camera Memory Interface Pixel Processor
 *
 * Copyright (C) STMicroelectronics SA 2023
 * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
 *          Alain Volmat <alain.volmat@foss.st.com>
 *          for STMicroelectronics.
 */

#include <linux/vmalloc.h>
#include <linux/v4l2-mediabus.h>
#include <media/v4l2-rect.h>
#include <media/v4l2-subdev.h>

#include "dcmipp-common.h"

#define DCMIPP_P0FCTCR  0x500
#define DCMIPP_P0FCTCR_FRATE_MASK       GENMASK(1, 0)
#define DCMIPP_P0SCSTR  0x504
#define DCMIPP_P0SCSTR_HSTART_SHIFT     0
#define DCMIPP_P0SCSTR_VSTART_SHIFT     16
#define DCMIPP_P0SCSZR  0x508
#define DCMIPP_P0SCSZR_ENABLE           BIT(31)
#define DCMIPP_P0SCSZR_HSIZE_SHIFT      0
#define DCMIPP_P0SCSZR_VSIZE_SHIFT      16
#define DCMIPP_P0PPCR   0x5c0
#define DCMIPP_P0PPCR_BSM_1_2           0x1
#define DCMIPP_P0PPCR_BSM_1_4           0x2
#define DCMIPP_P0PPCR_BSM_2_4           0x3
#define DCMIPP_P0PPCR_BSM_MASK          GENMASK(8, 7)
#define DCMIPP_P0PPCR_BSM_SHIFT         0x7
#define DCMIPP_P0PPCR_LSM               BIT(10)
#define DCMIPP_P0PPCR_OELS              BIT(11)

#define IS_SINK(pad) (!(pad))
#define IS_SRC(pad)  ((pad))

struct dcmipp_byteproc_pix_map {
        unsigned int code;
        unsigned int bpp;
};

#define PIXMAP_MBUS_BPP(mbus, byteperpixel)             \
        {                                               \
                .code = MEDIA_BUS_FMT_##mbus,           \
                .bpp = byteperpixel,                    \
        }
static const struct dcmipp_byteproc_pix_map dcmipp_byteproc_pix_map_list[] = {
        PIXMAP_MBUS_BPP(RGB565_2X8_LE, 2),
        PIXMAP_MBUS_BPP(RGB565_1X16, 2),
        PIXMAP_MBUS_BPP(RGB888_3X8, 3),
        PIXMAP_MBUS_BPP(RGB888_1X24, 3),
        PIXMAP_MBUS_BPP(YUYV8_2X8, 2),
        PIXMAP_MBUS_BPP(YUYV8_1X16, 2),
        PIXMAP_MBUS_BPP(YVYU8_2X8, 2),
        PIXMAP_MBUS_BPP(YVYU8_1X16, 2),
        PIXMAP_MBUS_BPP(UYVY8_2X8, 2),
        PIXMAP_MBUS_BPP(UYVY8_1X16, 2),
        PIXMAP_MBUS_BPP(VYUY8_2X8, 2),
        PIXMAP_MBUS_BPP(VYUY8_1X16, 2),
        PIXMAP_MBUS_BPP(Y8_1X8, 1),
        PIXMAP_MBUS_BPP(Y10_1X10, 2),
        PIXMAP_MBUS_BPP(Y12_1X12, 2),
        PIXMAP_MBUS_BPP(Y14_1X14, 2),
        PIXMAP_MBUS_BPP(SBGGR8_1X8, 1),
        PIXMAP_MBUS_BPP(SGBRG8_1X8, 1),
        PIXMAP_MBUS_BPP(SGRBG8_1X8, 1),
        PIXMAP_MBUS_BPP(SRGGB8_1X8, 1),
        PIXMAP_MBUS_BPP(SBGGR10_1X10, 2),
        PIXMAP_MBUS_BPP(SGBRG10_1X10, 2),
        PIXMAP_MBUS_BPP(SGRBG10_1X10, 2),
        PIXMAP_MBUS_BPP(SRGGB10_1X10, 2),
        PIXMAP_MBUS_BPP(SBGGR12_1X12, 2),
        PIXMAP_MBUS_BPP(SGBRG12_1X12, 2),
        PIXMAP_MBUS_BPP(SGRBG12_1X12, 2),
        PIXMAP_MBUS_BPP(SRGGB12_1X12, 2),
        PIXMAP_MBUS_BPP(SBGGR14_1X14, 2),
        PIXMAP_MBUS_BPP(SGBRG14_1X14, 2),
        PIXMAP_MBUS_BPP(SGRBG14_1X14, 2),
        PIXMAP_MBUS_BPP(SRGGB14_1X14, 2),
        PIXMAP_MBUS_BPP(JPEG_1X8, 1),
};

static const struct dcmipp_byteproc_pix_map *
dcmipp_byteproc_pix_map_by_code(u32 code)
{
        unsigned int i;

        for (i = 0; i < ARRAY_SIZE(dcmipp_byteproc_pix_map_list); i++) {
                if (dcmipp_byteproc_pix_map_list[i].code == code)
                        return &dcmipp_byteproc_pix_map_list[i];
        }

        return NULL;
}

struct dcmipp_byteproc_device {
        struct dcmipp_ent_device ved;
        struct v4l2_subdev sd;
        struct device *dev;
        void __iomem *regs;
};

static const struct v4l2_mbus_framefmt fmt_default = {
        .width = DCMIPP_FMT_WIDTH_DEFAULT,
        .height = DCMIPP_FMT_HEIGHT_DEFAULT,
        .code = MEDIA_BUS_FMT_RGB565_2X8_LE,
        .field = V4L2_FIELD_NONE,
        .colorspace = DCMIPP_COLORSPACE_DEFAULT,
        .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
        .quantization = DCMIPP_QUANTIZATION_DEFAULT,
        .xfer_func = DCMIPP_XFER_FUNC_DEFAULT,
};

static const struct v4l2_rect crop_min = {
        .width = DCMIPP_FRAME_MIN_WIDTH,
        .height = DCMIPP_FRAME_MIN_HEIGHT,
        .top = 0,
        .left = 0,
};

static void dcmipp_byteproc_adjust_crop(struct v4l2_rect *r,
                                        struct v4l2_rect *compose)
{
        /* Disallow rectangles smaller than the minimal one. */
        v4l2_rect_set_min_size(r, &crop_min);
        v4l2_rect_map_inside(r, compose);
}

static void dcmipp_byteproc_adjust_compose(struct v4l2_rect *r,
                                           const struct v4l2_mbus_framefmt *fmt)
{
        const struct dcmipp_byteproc_pix_map *vpix;

        r->top = 0;
        r->left = 0;

        /* Compose is not possible for JPEG or Bayer formats */
        if (fmt->code >= MEDIA_BUS_FMT_SBGGR8_1X8 &&
            fmt->code <= MEDIA_BUS_FMT_JPEG_1X8) {
                r->width = fmt->width;
                r->height = fmt->height;
                return;
        }

        /* Prevent compose on formats which are not 1 or 2 bytes per pixel */
        vpix = dcmipp_byteproc_pix_map_by_code(fmt->code);
        if (vpix->bpp != 1 && vpix->bpp != 2) {
                r->width = fmt->width;
                r->height = fmt->height;
                return;
        }

        /* Adjust height - we can only perform 1/2 decimation */
        if (r->height <= (fmt->height / 2))
                r->height = fmt->height / 2;
        else
                r->height = fmt->height;

        /* Adjust width /2 or /4 for 8bits formats and /2 for 16bits formats */
        if (vpix->bpp == 1 && r->width <= (fmt->width / 4))
                r->width = fmt->width / 4;
        else if (r->width <= (fmt->width / 2))
                r->width = fmt->width / 2;
        else
                r->width = fmt->width;
}

static void dcmipp_byteproc_adjust_fmt(struct v4l2_mbus_framefmt *fmt)
{
        const struct dcmipp_byteproc_pix_map *vpix;

        /* Only accept code in the pix map table */
        vpix = dcmipp_byteproc_pix_map_by_code(fmt->code);
        if (!vpix)
                fmt->code = fmt_default.code;

        fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH,
                             DCMIPP_FRAME_MAX_WIDTH) & ~1;
        fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT,
                              DCMIPP_FRAME_MAX_HEIGHT) & ~1;

        if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
                fmt->field = fmt_default.field;

        dcmipp_colorimetry_clamp(fmt);
}

static int dcmipp_byteproc_init_state(struct v4l2_subdev *sd,
                                      struct v4l2_subdev_state *sd_state)
{
        unsigned int i;

        for (i = 0; i < sd->entity.num_pads; i++) {
                struct v4l2_mbus_framefmt *mf;
                struct v4l2_rect *r;

                mf = v4l2_subdev_state_get_format(sd_state, i);
                *mf = fmt_default;

                if (IS_SINK(i))
                        r = v4l2_subdev_state_get_compose(sd_state, i);
                else
                        r = v4l2_subdev_state_get_crop(sd_state, i);

                r->top = 0;
                r->left = 0;
                r->width = DCMIPP_FMT_WIDTH_DEFAULT;
                r->height = DCMIPP_FMT_HEIGHT_DEFAULT;
        }

        return 0;
}

static int
dcmipp_byteproc_enum_mbus_code(struct v4l2_subdev *sd,
                               struct v4l2_subdev_state *sd_state,
                               struct v4l2_subdev_mbus_code_enum *code)
{
        const struct dcmipp_byteproc_pix_map *vpix;
        struct v4l2_mbus_framefmt *sink_fmt;

        if (IS_SINK(code->pad)) {
                if (code->index >= ARRAY_SIZE(dcmipp_byteproc_pix_map_list))
                        return -EINVAL;
                vpix = &dcmipp_byteproc_pix_map_list[code->index];
                code->code = vpix->code;
        } else {
                /* byteproc doesn't support transformation on format */
                if (code->index > 0)
                        return -EINVAL;

                sink_fmt = v4l2_subdev_state_get_format(sd_state, 0);
                code->code = sink_fmt->code;
        }

        return 0;
}

static int
dcmipp_byteproc_enum_frame_size(struct v4l2_subdev *sd,
                                struct v4l2_subdev_state *sd_state,
                                struct v4l2_subdev_frame_size_enum *fse)
{
        struct v4l2_rect *compose;

        if (fse->index)
                return -EINVAL;

        fse->min_width = DCMIPP_FRAME_MIN_WIDTH;
        fse->min_height = DCMIPP_FRAME_MIN_HEIGHT;

        if (IS_SINK(fse->pad)) {
                fse->max_width = DCMIPP_FRAME_MAX_WIDTH;
                fse->max_height = DCMIPP_FRAME_MAX_HEIGHT;
        } else {
                compose = v4l2_subdev_state_get_compose(sd_state, 0);
                fse->max_width = compose->width;
                fse->max_height = compose->height;
        }

        return 0;
}

static int dcmipp_byteproc_set_fmt(struct v4l2_subdev *sd,
                                   struct v4l2_subdev_state *sd_state,
                                   struct v4l2_subdev_format *fmt)
{
        struct v4l2_mbus_framefmt *mf;
        struct v4l2_rect *crop, *compose;

        if (v4l2_subdev_is_streaming(sd))
                return -EBUSY;

        mf = v4l2_subdev_state_get_format(sd_state, fmt->pad);

        crop = v4l2_subdev_state_get_crop(sd_state, 1);
        compose = v4l2_subdev_state_get_compose(sd_state, 0);

        if (IS_SRC(fmt->pad)) {
                fmt->format = *v4l2_subdev_state_get_format(sd_state, 0);
                fmt->format.width = crop->width;
                fmt->format.height = crop->height;
        } else {
                dcmipp_byteproc_adjust_fmt(&fmt->format);
                crop->top = 0;
                crop->left = 0;
                crop->width = fmt->format.width;
                crop->height = fmt->format.height;
                *compose = *crop;
                /* Set the same format on SOURCE pad as well */
                *v4l2_subdev_state_get_format(sd_state, 1) = fmt->format;
        }
        *mf = fmt->format;

        return 0;
}

static int dcmipp_byteproc_get_selection(struct v4l2_subdev *sd,
                                         struct v4l2_subdev_state *sd_state,
                                         struct v4l2_subdev_selection *s)
{
        struct v4l2_mbus_framefmt *sink_fmt;
        struct v4l2_rect *crop, *compose;

        /*
         * In the HW, the decimation block is located prior to the crop hence:
         * Compose is done on the sink pad
         * Crop is done on the src pad
         */
        if (IS_SINK(s->pad) &&
            (s->target == V4L2_SEL_TGT_CROP ||
             s->target == V4L2_SEL_TGT_CROP_BOUNDS ||
             s->target == V4L2_SEL_TGT_CROP_DEFAULT))
                return -EINVAL;

        if (IS_SRC(s->pad) &&
            (s->target == V4L2_SEL_TGT_COMPOSE ||
             s->target == V4L2_SEL_TGT_COMPOSE_BOUNDS ||
             s->target == V4L2_SEL_TGT_COMPOSE_DEFAULT))
                return -EINVAL;

        sink_fmt = v4l2_subdev_state_get_format(sd_state, 0);
        crop = v4l2_subdev_state_get_crop(sd_state, 1);
        compose = v4l2_subdev_state_get_compose(sd_state, 0);

        switch (s->target) {
        case V4L2_SEL_TGT_CROP:
                s->r = *crop;
                break;
        case V4L2_SEL_TGT_CROP_BOUNDS:
        case V4L2_SEL_TGT_CROP_DEFAULT:
                s->r = *compose;
                break;
        case V4L2_SEL_TGT_COMPOSE:
                s->r = *compose;
                break;
        case V4L2_SEL_TGT_COMPOSE_BOUNDS:
        case V4L2_SEL_TGT_COMPOSE_DEFAULT:
                s->r.top = 0;
                s->r.left = 0;
                s->r.width = sink_fmt->width;
                s->r.height = sink_fmt->height;
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static int dcmipp_byteproc_set_selection(struct v4l2_subdev *sd,
                                         struct v4l2_subdev_state *sd_state,
                                         struct v4l2_subdev_selection *s)
{
        struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
        struct v4l2_mbus_framefmt *mf;
        struct v4l2_rect *crop, *compose;

        /*
         * In the HW, the decimation block is located prior to the crop hence:
         * Compose is done on the sink pad
         * Crop is done on the src pad
         */
        if ((s->target == V4L2_SEL_TGT_CROP ||
             s->target == V4L2_SEL_TGT_CROP_BOUNDS ||
             s->target == V4L2_SEL_TGT_CROP_DEFAULT) && IS_SINK(s->pad))
                return -EINVAL;

        if ((s->target == V4L2_SEL_TGT_COMPOSE ||
             s->target == V4L2_SEL_TGT_COMPOSE_BOUNDS ||
             s->target == V4L2_SEL_TGT_COMPOSE_DEFAULT) && IS_SRC(s->pad))
                return -EINVAL;

        crop = v4l2_subdev_state_get_crop(sd_state, 1);
        compose = v4l2_subdev_state_get_compose(sd_state, 0);

        switch (s->target) {
        case V4L2_SEL_TGT_CROP:
                dcmipp_byteproc_adjust_crop(&s->r, compose);

                *crop = s->r;
                mf = v4l2_subdev_state_get_format(sd_state, 1);
                mf->width = s->r.width;
                mf->height = s->r.height;

                dev_dbg(byteproc->dev, "s_selection: crop (%d,%d)/%ux%u\n",
                        crop->left, crop->top, crop->width, crop->height);
                break;
        case V4L2_SEL_TGT_COMPOSE:
                mf = v4l2_subdev_state_get_format(sd_state, 0);
                dcmipp_byteproc_adjust_compose(&s->r, mf);
                *compose = s->r;
                *crop = s->r;

                mf = v4l2_subdev_state_get_format(sd_state, 1);
                mf->width = s->r.width;
                mf->height = s->r.height;

                dev_dbg(byteproc->dev, "s_selection: compose (%d,%d)/%ux%u\n",
                        compose->left, compose->top,
                        compose->width, compose->height);
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static int dcmipp_byteproc_configure_scale_crop
                        (struct dcmipp_byteproc_device *byteproc,
                         struct v4l2_subdev_state *state)
{
        const struct dcmipp_byteproc_pix_map *vpix;
        struct v4l2_mbus_framefmt *sink_fmt;
        u32 hprediv, vprediv;
        struct v4l2_rect *compose, *crop;
        u32 val = 0;

        sink_fmt = v4l2_subdev_state_get_format(state, 0);
        compose = v4l2_subdev_state_get_compose(state, 0);
        crop = v4l2_subdev_state_get_crop(state, 1);

        /* find output format bpp */
        vpix = dcmipp_byteproc_pix_map_by_code(sink_fmt->code);
        if (!vpix)
                return -EINVAL;

        /* clear decimation/crop */
        reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_BSM_MASK);
        reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_LSM);
        reg_write(byteproc, DCMIPP_P0SCSTR, 0);
        reg_write(byteproc, DCMIPP_P0SCSZR, 0);

        /* Ignore decimation/crop with JPEG */
        if (vpix->code == MEDIA_BUS_FMT_JPEG_1X8)
                return 0;

        /* decimation */
        hprediv = sink_fmt->width / compose->width;
        if (hprediv == 4)
                val |= DCMIPP_P0PPCR_BSM_1_4 << DCMIPP_P0PPCR_BSM_SHIFT;
        else if ((vpix->code == MEDIA_BUS_FMT_Y8_1X8) && (hprediv == 2))
                val |= DCMIPP_P0PPCR_BSM_1_2 << DCMIPP_P0PPCR_BSM_SHIFT;
        else if (hprediv == 2)
                val |= DCMIPP_P0PPCR_BSM_2_4 << DCMIPP_P0PPCR_BSM_SHIFT;

        vprediv = sink_fmt->height / compose->height;
        if (vprediv == 2)
                val |= DCMIPP_P0PPCR_LSM | DCMIPP_P0PPCR_OELS;

        /* decimate using bytes and lines skipping */
        if (val) {
                reg_set(byteproc, DCMIPP_P0PPCR, val);

                dev_dbg(byteproc->dev, "decimate to %dx%d [prediv=%dx%d]\n",
                        compose->width, compose->height,
                        hprediv, vprediv);
        }

        dev_dbg(byteproc->dev, "crop to %dx%d\n", crop->width, crop->height);

        /* expressed in 32-bits words on X axis, lines on Y axis */
        reg_write(byteproc, DCMIPP_P0SCSTR,
                  (((crop->left * vpix->bpp) / 4) <<
                   DCMIPP_P0SCSTR_HSTART_SHIFT) |
                  (crop->top << DCMIPP_P0SCSTR_VSTART_SHIFT));
        reg_write(byteproc, DCMIPP_P0SCSZR,
                  DCMIPP_P0SCSZR_ENABLE |
                  (((crop->width * vpix->bpp) / 4) <<
                   DCMIPP_P0SCSZR_HSIZE_SHIFT) |
                  (crop->height << DCMIPP_P0SCSZR_VSIZE_SHIFT));

        return 0;
}

static int dcmipp_byteproc_enable_streams(struct v4l2_subdev *sd,
                                          struct v4l2_subdev_state *state,
                                          u32 pad, u64 streams_mask)
{
        struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
        struct v4l2_subdev *s_subdev;
        struct media_pad *s_pad;
        int ret;

        /* Get source subdev */
        s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]);
        if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity))
                return -EINVAL;
        s_subdev = media_entity_to_v4l2_subdev(s_pad->entity);

        ret = dcmipp_byteproc_configure_scale_crop(byteproc, state);
        if (ret)
                return ret;

        ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0));
        if (ret < 0) {
                dev_err(byteproc->dev,
                        "failed to start source subdev streaming (%d)\n", ret);
                return ret;
        }

        return 0;
}

static int dcmipp_byteproc_disable_streams(struct v4l2_subdev *sd,
                                           struct v4l2_subdev_state *state,
                                           u32 pad, u64 streams_mask)
{
        struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
        struct v4l2_subdev *s_subdev;
        struct media_pad *s_pad;
        int ret;

        /* Get source subdev */
        s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]);
        if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity))
                return -EINVAL;
        s_subdev = media_entity_to_v4l2_subdev(s_pad->entity);

        ret = v4l2_subdev_disable_streams(s_subdev, s_pad->index, BIT_ULL(0));
        if (ret < 0) {
                dev_err(byteproc->dev,
                        "failed to start source subdev streaming (%d)\n", ret);
                return ret;
        }

        return 0;
}

static const struct v4l2_subdev_pad_ops dcmipp_byteproc_pad_ops = {
        .enum_mbus_code         = dcmipp_byteproc_enum_mbus_code,
        .enum_frame_size        = dcmipp_byteproc_enum_frame_size,
        .get_fmt                = v4l2_subdev_get_fmt,
        .set_fmt                = dcmipp_byteproc_set_fmt,
        .get_selection          = dcmipp_byteproc_get_selection,
        .set_selection          = dcmipp_byteproc_set_selection,
        .enable_streams         = dcmipp_byteproc_enable_streams,
        .disable_streams        = dcmipp_byteproc_disable_streams,
};

static const struct v4l2_subdev_video_ops dcmipp_byteproc_video_ops = {
        .s_stream = v4l2_subdev_s_stream_helper,
};

static const struct v4l2_subdev_ops dcmipp_byteproc_ops = {
        .pad = &dcmipp_byteproc_pad_ops,
        .video = &dcmipp_byteproc_video_ops,
};

static void dcmipp_byteproc_release(struct v4l2_subdev *sd)
{
        struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);

        kfree(byteproc);
}

static const struct v4l2_subdev_internal_ops dcmipp_byteproc_int_ops = {
        .init_state = dcmipp_byteproc_init_state,
        .release = dcmipp_byteproc_release,
};

void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved)
{
        struct dcmipp_byteproc_device *byteproc =
                        container_of(ved, struct dcmipp_byteproc_device, ved);

        dcmipp_ent_sd_unregister(ved, &byteproc->sd);
}

struct dcmipp_ent_device *
dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name,
                         struct v4l2_device *v4l2_dev, void __iomem *regs)
{
        struct dcmipp_byteproc_device *byteproc;
        const unsigned long pads_flag[] = {
                MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE,
        };
        int ret;

        /* Allocate the byteproc struct */
        byteproc = kzalloc_obj(*byteproc);
        if (!byteproc)
                return ERR_PTR(-ENOMEM);

        byteproc->regs = regs;

        /* Initialize ved and sd */
        ret = dcmipp_ent_sd_register(&byteproc->ved, &byteproc->sd,
                                     v4l2_dev, entity_name,
                                     MEDIA_ENT_F_PROC_VIDEO_SCALER,
                                     ARRAY_SIZE(pads_flag), pads_flag,
                                     &dcmipp_byteproc_int_ops,
                                     &dcmipp_byteproc_ops,
                                     NULL, NULL);
        if (ret) {
                kfree(byteproc);
                return ERR_PTR(ret);
        }

        byteproc->dev = dev;

        return &byteproc->ved;
}