| // 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(YUYV8_2X8, 2), |
| PIXMAP_MBUS_BPP(YVYU8_2X8, 2), |
| PIXMAP_MBUS_BPP(UYVY8_2X8, 2), |
| PIXMAP_MBUS_BPP(VYUY8_2X8, 2), |
| PIXMAP_MBUS_BPP(Y8_1X8, 1), |
| 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(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; |
| bool streaming; |
| }; |
| |
| 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) |
| { |
| r->top = 0; |
| r->left = 0; |
| |
| /* Compose is not possible for JPEG or Bayer formats */ |
| if (fmt->code == MEDIA_BUS_FMT_JPEG_1X8 || |
| fmt->code == MEDIA_BUS_FMT_SBGGR8_1X8 || |
| fmt->code == MEDIA_BUS_FMT_SGBRG8_1X8 || |
| fmt->code == MEDIA_BUS_FMT_SGRBG8_1X8 || |
| fmt->code == MEDIA_BUS_FMT_SRGGB8_1X8) { |
| 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 (fmt->code == MEDIA_BUS_FMT_Y8_1X8 && 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 dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd); |
| struct v4l2_mbus_framefmt *mf; |
| struct v4l2_rect *crop, *compose; |
| |
| if (byteproc->streaming) |
| 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 %ux%u@(%u,%u)\n", |
| crop->width, crop->height, crop->left, crop->top); |
| 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 %ux%u@(%u,%u)\n", |
| compose->width, compose->height, |
| compose->left, compose->top); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| 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, |
| }; |
| |
| static int dcmipp_byteproc_configure_scale_crop |
| (struct dcmipp_byteproc_device *byteproc) |
| { |
| const struct dcmipp_byteproc_pix_map *vpix; |
| struct v4l2_subdev_state *state; |
| struct v4l2_mbus_framefmt *sink_fmt; |
| u32 hprediv, vprediv; |
| struct v4l2_rect *compose, *crop; |
| u32 val = 0; |
| |
| state = v4l2_subdev_lock_and_get_active_state(&byteproc->sd); |
| 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); |
| v4l2_subdev_unlock_state(state); |
| |
| /* 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_s_stream(struct v4l2_subdev *sd, int enable) |
| { |
| struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd); |
| struct v4l2_subdev *s_subdev; |
| struct media_pad *pad; |
| int ret = 0; |
| |
| /* Get source subdev */ |
| pad = media_pad_remote_pad_first(&sd->entity.pads[0]); |
| if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) |
| return -EINVAL; |
| s_subdev = media_entity_to_v4l2_subdev(pad->entity); |
| |
| if (enable) { |
| ret = dcmipp_byteproc_configure_scale_crop(byteproc); |
| if (ret) |
| return ret; |
| |
| ret = v4l2_subdev_call(s_subdev, video, s_stream, enable); |
| if (ret < 0) { |
| dev_err(byteproc->dev, |
| "failed to start source subdev streaming (%d)\n", |
| ret); |
| return ret; |
| } |
| } else { |
| ret = v4l2_subdev_call(s_subdev, video, s_stream, enable); |
| if (ret < 0) { |
| dev_err(byteproc->dev, |
| "failed to stop source subdev streaming (%d)\n", |
| ret); |
| return ret; |
| } |
| } |
| |
| byteproc->streaming = enable; |
| |
| return 0; |
| } |
| |
| static const struct v4l2_subdev_video_ops dcmipp_byteproc_video_ops = { |
| .s_stream = dcmipp_byteproc_s_stream, |
| }; |
| |
| 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(sizeof(*byteproc), GFP_KERNEL); |
| 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; |
| } |