// SPDX-License-Identifier: GPL-2.0 /* * Driver for STM32 Digital Camera Memory Interface Pixel Processor * * Copyright (C) STMicroelectronics SA 2023 * Authors: Hugues Fruchet * Alain Volmat * for STMicroelectronics. */ #include #include #include #include #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; }