Hi Sakari - thanks for the review. Snipping some bits for which I have no comment... On 23/05/2024 09:03, Sakari Ailus wrote: <snip>
+ +static unsigned int mali_c55_calculate_bank_num(struct mali_c55 *mali_c55, + unsigned int crop, + unsigned int scale) +{ + unsigned int tmp; + unsigned int i; + + tmp = (scale * 1000) / crop;This looks like something that can overflow. Can it?
Shouldn't be able to; maximum scale width is 8192.
+ + for (i = 0; i < ARRAY_SIZE(mali_c55_coefficient_banks); i++) { + if (tmp >= mali_c55_coefficient_banks[i].bottom && + tmp <= mali_c55_coefficient_banks[i].top) + return mali_c55_coefficient_banks[i].bank; + } + + /* + * We shouldn't ever get here, in theory. As we have no good choices + * simply warn the user and use the first bank of coefficients. + */ + dev_warn(mali_c55->dev, "scaling factor outside defined bounds\n"); + return 0; +} + +#endif /* _MALI_C55_RESIZER_COEFS_H */ diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c new file mode 100644 index 000000000000..8e0669a5f391 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c @@ -0,0 +1,740 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - Image signal processor + * + * Copyright (C) 2024 Ideas on Board Oy + */ + +#include <linux/math.h> +#include <linux/minmax.h> + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "mali-c55-common.h" +#include "mali-c55-registers.h" +#include "mali-c55-resizer-coefs.h" + +/* Scaling factor in Q4.20 format. */ +#define MALI_C55_RZR_SCALER_FACTOR 1048576(1U << 20) ?+ +static const u32 rzr_non_bypass_src_fmts[] = { + MEDIA_BUS_FMT_RGB121212_1X36, + MEDIA_BUS_FMT_YUV10_1X30 +}; + +static const char * const mali_c55_resizer_names[] = { + [MALI_C55_RZR_FR] = "resizer fr", + [MALI_C55_RZR_DS] = "resizer ds", +}; + +static int mali_c55_rzr_program_crop(struct mali_c55_resizer *rzr, + struct v4l2_subdev_state *state) +{ + unsigned int reg_offset = rzr->cap_dev->reg_offset; + struct mali_c55 *mali_c55 = rzr->mali_c55; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + + /* Verify if crop should be enabled. */ + fmt = v4l2_subdev_state_get_format(state, MALI_C55_RZR_SINK_PAD, 0); + crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD, 0); + + if (fmt->width == crop->width && fmt->height == crop->height) + return MALI_C55_BYPASS_CROP; + + mali_c55_write(mali_c55, MALI_C55_REG_CROP_X_START(reg_offset), + crop->left); + mali_c55_write(mali_c55, MALI_C55_REG_CROP_Y_START(reg_offset), + crop->top); + mali_c55_write(mali_c55, MALI_C55_REG_CROP_X_SIZE(reg_offset), + crop->width); + mali_c55_write(mali_c55, MALI_C55_REG_CROP_Y_SIZE(reg_offset), + crop->height); + + mali_c55_write(mali_c55, MALI_C55_REG_CROP_EN(reg_offset), + MALI_C55_CROP_ENABLE); + + return 0; +} + +static int mali_c55_rzr_program_resizer(struct mali_c55_resizer *rzr, + struct v4l2_subdev_state *state) +{ + unsigned int reg_offset = rzr->cap_dev->reg_offset; + struct mali_c55 *mali_c55 = rzr->mali_c55; + struct v4l2_rect *crop, *scale; + unsigned int h_bank, v_bank; + u64 h_scale, v_scale; + + /* Verify if scaling should be enabled. */ + crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD, 0); + scale = v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD, 0); + + if (crop->width == scale->width && crop->height == scale->height) + return MALI_C55_BYPASS_SCALER; + + /* Program the V/H scaling factor in Q4.20 format. */ + h_scale = crop->width * MALI_C55_RZR_SCALER_FACTOR; + v_scale = crop->height * MALI_C55_RZR_SCALER_FACTOR; + + do_div(h_scale, scale->width); + do_div(v_scale, scale->height); + + mali_c55_write(mali_c55, + MALI_C55_REG_SCALER_IN_WIDTH(reg_offset), + crop->width); + mali_c55_write(mali_c55, + MALI_C55_REG_SCALER_IN_HEIGHT(reg_offset), + crop->height); + + mali_c55_write(mali_c55, + MALI_C55_REG_SCALER_OUT_WIDTH(reg_offset), + scale->width); + mali_c55_write(mali_c55, + MALI_C55_REG_SCALER_OUT_HEIGHT(reg_offset), + scale->height); + + mali_c55_write(mali_c55, + MALI_C55_REG_SCALER_HFILT_TINC(reg_offset), + h_scale); + mali_c55_write(mali_c55, + MALI_C55_REG_SCALER_VFILT_TINC(reg_offset), + v_scale); + + h_bank = mali_c55_calculate_bank_num(mali_c55, crop->width, + scale->width); + mali_c55_write(mali_c55, + MALI_C55_REG_SCALER_HFILT_COEF(reg_offset), + h_bank); + + v_bank = mali_c55_calculate_bank_num(mali_c55, crop->height, + scale->height); + mali_c55_write(mali_c55, + MALI_C55_REG_SCALER_VFILT_COEF(reg_offset), + v_bank); + + return 0; +} + +static void mali_c55_rzr_program(struct mali_c55_resizer *rzr, + struct v4l2_subdev_state *state) +{ + struct mali_c55 *mali_c55 = rzr->mali_c55; + u32 bypass = 0; + + /* Verify if cropping and scaling should be enabled. */ + bypass |= mali_c55_rzr_program_crop(rzr, state); + bypass |= mali_c55_rzr_program_resizer(rzr, state); + + mali_c55_update_bits(mali_c55, rzr->id == MALI_C55_RZR_FR ? + MALI_C55_REG_FR_BYPASS : MALI_C55_REG_DS_BYPASS, + MALI_C55_BYPASS_CROP | MALI_C55_BYPASS_SCALER, + bypass); +} + +/* + * Inspect the routing table to know which of the two (mutually exclusive) + * routes is enabled and return the sink pad id of the active route. + */ +static unsigned int mali_c55_rzr_get_active_sink(struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_krouting *routing = &state->routing; + struct v4l2_subdev_route *route; + + /* A single route is enabled at a time. */ + for_each_active_route(routing, route) + return route->sink_pad; + + return MALI_C55_RZR_SINK_PAD; +} + +static int __mali_c55_rzr_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer, + sd); + unsigned int active_sink = UINT_MAX; + struct v4l2_rect *crop, *compose; + struct v4l2_subdev_route *route; + unsigned int active_routes = 0; + struct v4l2_mbus_framefmt *fmt; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, 0); + if (ret) + return ret; + + /* Only a single route can be enabled at a time. */ + for_each_active_route(routing, route) { + if (++active_routes > 1) { + dev_err(rzr->mali_c55->dev, + "Only one route can be active"); + return -EINVAL; + } + + active_sink = route->sink_pad; + } + if (active_sink == UINT_MAX) { + dev_err(rzr->mali_c55->dev, "One route has to be active"); + return -EINVAL; + } + + ret = v4l2_subdev_set_routing(sd, state, routing); + if (ret) { + dev_err(rzr->mali_c55->dev, "Failed to set routing\n"); + return ret; + } + + fmt = v4l2_subdev_state_get_format(state, active_sink, 0); + crop = v4l2_subdev_state_get_crop(state, active_sink, 0); + compose = v4l2_subdev_state_get_compose(state, active_sink, 0); + + fmt->width = MALI_C55_DEFAULT_WIDTH; + fmt->height = MALI_C55_DEFAULT_HEIGHT; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->field = V4L2_FIELD_NONE; + + if (active_sink == MALI_C55_RZR_SINK_PAD) { + fmt->code = MEDIA_BUS_FMT_RGB121212_1X36; + + crop->left = crop->top = 0; + crop->width = MALI_C55_DEFAULT_WIDTH; + crop->height = MALI_C55_DEFAULT_HEIGHT; + + *compose = *crop; + } else { + fmt->code = MEDIA_BUS_FMT_SRGGB12_1X12; + } + + /* Propagate the format to the source pad */ + *v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD, 0) = *fmt; + + return 0; +} + +static int mali_c55_rzr_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct v4l2_mbus_framefmt *sink_fmt; + const struct mali_c55_isp_fmt *fmt; + unsigned int index = 0; + u32 sink_pad; + + switch (code->pad) { + case MALI_C55_RZR_SINK_PAD: + if (code->index) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_RGB121212_1X36; + + return 0; + case MALI_C55_RZR_SOURCE_PAD: + sink_pad = mali_c55_rzr_get_active_sink(state); + sink_fmt = v4l2_subdev_state_get_format(state, sink_pad, 0); + + /* + * If the active route is from the Bypass sink pad, then the + * source pad is a simple passthrough of the sink format. + */ + + if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) { + if (code->index) + return -EINVAL; + + code->code = sink_fmt->code; + return 0; + } + + /* + * If the active route is from the non-bypass sink then we can + * select either RGB or conversion to YUV. + */ + + if (code->index >= ARRAY_SIZE(rzr_non_bypass_src_fmts)) + return -EINVAL; + + code->code = rzr_non_bypass_src_fmts[code->index]; + + return 0; + case MALI_C55_RZR_SINK_BYPASS_PAD: + for_each_mali_isp_fmt(fmt) { + if (index++ == code->index) { + code->code = fmt->code; + return 0; + } + } + + break; + } + + return -EINVAL; +} + +static int mali_c55_rzr_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index) + return -EINVAL; + + fse->max_width = MALI_C55_MAX_WIDTH; + fse->max_height = MALI_C55_MAX_HEIGHT; + fse->min_width = MALI_C55_MIN_WIDTH; + fse->min_height = MALI_C55_MIN_HEIGHT; + + return 0; +} + +static int mali_c55_rzr_set_sink_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct v4l2_rect *rect; + unsigned int sink_pad; + + /* + * Clamp to min/max and then reset crop and compose rectangles to the + * newly applied size. + */ + clamp_t(unsigned int, fmt->width, + MALI_C55_MIN_WIDTH, MALI_C55_MAX_WIDTH); + clamp_t(unsigned int, fmt->height, + MALI_C55_MIN_HEIGHT, MALI_C55_MAX_HEIGHT); + + sink_pad = mali_c55_rzr_get_active_sink(state); + if (sink_pad == MALI_C55_RZR_SINK_PAD) { + fmt->code = MEDIA_BUS_FMT_RGB121212_1X36; + + rect = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD); + rect->left = 0; + rect->top = 0; + rect->width = fmt->width; + rect->height = fmt->height; + + rect = v4l2_subdev_state_get_compose(state, + MALI_C55_RZR_SINK_PAD); + rect->left = 0; + rect->top = 0; + rect->width = fmt->width; + rect->height = fmt->height; + } else { + /* + * Make sure the media bus code is one of the supported + * ISP input media bus codes. + */ + if (!mali_c55_isp_is_format_supported(fmt->code)) + fmt->code = MALI_C55_DEFAULT_MEDIA_BUS_FMT; + } + + *v4l2_subdev_state_get_format(state, sink_pad, 0) = *fmt; + *v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD, 0) = *fmt; + + return 0; +} + +static int mali_c55_rzr_set_source_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer, + sd); + struct v4l2_mbus_framefmt *fmt = &format->format; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *crop, *compose; + unsigned int sink_pad; + unsigned int i; + + sink_pad = mali_c55_rzr_get_active_sink(state); + sink_fmt = v4l2_subdev_state_get_format(state, sink_pad, 0); + crop = v4l2_subdev_state_get_crop(state, sink_pad, 0); + compose = v4l2_subdev_state_get_compose(state, sink_pad, 0); + + /* FR Bypass pipe. */ + + if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) { + /* + * Format on the source pad is the same as the one on the + * sink pad. + */ + fmt->code = sink_fmt->code; + + /* RAW bypass disables scaling and cropping. */ + crop->top = compose->top = 0; + crop->left = compose->left = 0; + fmt->width = crop->width = compose->width = sink_fmt->width; + fmt->height = crop->height = compose->height = sink_fmt->height; + + *v4l2_subdev_state_get_format(state, + MALI_C55_RZR_SOURCE_PAD) = *fmt; + + return 0; + } + + /* Regular processing pipe. */ + + for (i = 0; i < ARRAY_SIZE(rzr_non_bypass_src_fmts); i++) { + if (fmt->code == rzr_non_bypass_src_fmts[i]) + break; + } + + if (i == ARRAY_SIZE(rzr_non_bypass_src_fmts)) { + dev_dbg(rzr->mali_c55->dev, + "Unsupported mbus code 0x%x: using default\n", + fmt->code); + fmt->code = MEDIA_BUS_FMT_RGB121212_1X36; + } + + /* + * The source pad format size comes directly from the sink pad + * compose rectangle. + */ + fmt->width = compose->width; + fmt->height = compose->height; + + *v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD) = *fmt; + + return 0; +} + +static int mali_c55_rzr_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + /* + * On sink pads fmt is either fixed for the 'regular' processing + * pad or a RAW format or 20-bit wide RGB/YUV format for the FR bypass + * pad. + * + * On source pad sizes are the result of crop+compose on the sink + * pad sizes, while the format depends on the active route. + */ + + if (format->pad != MALI_C55_RZR_SOURCE_PAD) + return mali_c55_rzr_set_sink_fmt(sd, state, format); + + return mali_c55_rzr_set_source_fmt(sd, state, format); +} + +static int mali_c55_rzr_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + if (sel->pad != MALI_C55_RZR_SINK_PAD) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP && + sel->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + + sel->r = sel->target == V4L2_SEL_TGT_CROP + ? *v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD) + : *v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD); + + return 0; +} + +static int mali_c55_rzr_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer, + sd); + struct v4l2_mbus_framefmt *source_fmt; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *crop, *compose; + + if (sel->pad != MALI_C55_RZR_SINK_PAD) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP && + sel->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + + source_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_RZR_SOURCE_PAD); + sink_fmt = v4l2_subdev_state_get_format(state, MALI_C55_RZR_SINK_PAD); + crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD); + compose = v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD); + + /* RAW bypass disables crop/scaling. */ + if (mali_c55_format_is_raw(source_fmt->code)) { + crop->top = compose->top = 0; + crop->left = compose->left = 0; + crop->width = compose->width = sink_fmt->width; + crop->height = compose->height = sink_fmt->height; + + sel->r = sel->target == V4L2_SEL_TGT_CROP ? *crop : *compose; + + return 0; + } + + /* During streaming, it is allowed to only change the crop rectangle. */ + if (rzr->streaming && sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* + * Update the desired target and then clamp the crop rectangle to the + * sink format sizes and the compose size to the crop sizes. + */ + if (sel->target == V4L2_SEL_TGT_CROP) + *crop = sel->r; + else + *compose = sel->r; + + clamp_t(unsigned int, crop->left, 0, sink_fmt->width); + clamp_t(unsigned int, crop->top, 0, sink_fmt->height); + clamp_t(unsigned int, crop->width, MALI_C55_MIN_WIDTH, + sink_fmt->width - crop->left); + clamp_t(unsigned int, crop->height, MALI_C55_MIN_HEIGHT, + sink_fmt->height - crop->top); + + if (rzr->streaming) { + /* + * Apply at runtime a crop rectangle on the resizer's sink only + * if it doesn't require re-programming the scaler output sizes + * as it would require changing the output buffer sizes as well. + */ + if (sel->r.width < compose->width || + sel->r.height < compose->height) + return -EINVAL; + + *crop = sel->r; + mali_c55_rzr_program(rzr, state); + + return 0; + } + + compose->left = 0; + compose->top = 0; + clamp_t(unsigned int, compose->left, 0, sink_fmt->width); + clamp_t(unsigned int, compose->top, 0, sink_fmt->height); + clamp_t(unsigned int, compose->width, crop->width / 8, crop->width); + clamp_t(unsigned int, compose->height, crop->height / 8, crop->height); + + sel->r = sel->target == V4L2_SEL_TGT_CROP ? *crop : *compose; + + return 0; +} + +static int mali_c55_rzr_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && + media_entity_is_streaming(&sd->entity)) + return -EBUSY; + + return __mali_c55_rzr_set_routing(sd, state, routing); +} + +static const struct v4l2_subdev_pad_ops mali_c55_resizer_pad_ops = { + .enum_mbus_code = mali_c55_rzr_enum_mbus_code, + .enum_frame_size = mali_c55_rzr_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mali_c55_rzr_set_fmt, + .get_selection = mali_c55_rzr_get_selection, + .set_selection = mali_c55_rzr_set_selection, + .set_routing = mali_c55_rzr_set_routing, +}; + +void mali_c55_rzr_start_stream(struct mali_c55_resizer *rzr) +{ + struct mali_c55 *mali_c55 = rzr->mali_c55; + struct v4l2_subdev *sd = &rzr->sd; + struct v4l2_subdev_state *state; + unsigned int sink_pad; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + sink_pad = mali_c55_rzr_get_active_sink(state); + if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) { + /* Bypass FR pipe processing if the bypass route is active. */ + mali_c55_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS, + MALI_C55_ISP_RAW_BYPASS_FR_BYPASS_MASK, + MALI_C55_ISP_RAW_BYPASS_RAW_FR_BYPASS); + goto unlock_state; + } + + /* Disable bypass and use regular processing. */ + mali_c55_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS, + MALI_C55_ISP_RAW_BYPASS_FR_BYPASS_MASK, 0); + mali_c55_rzr_program(rzr, state); + +unlock_state: + rzr->streaming = true; + v4l2_subdev_unlock_state(state); +} + +void mali_c55_rzr_stop_stream(struct mali_c55_resizer *rzr) +{ + struct v4l2_subdev *sd = &rzr->sd; + struct v4l2_subdev_state *state; + + state = v4l2_subdev_lock_and_get_active_state(sd); + rzr->streaming = false; + v4l2_subdev_unlock_state(state); +} + +static const struct v4l2_subdev_ops mali_c55_resizer_ops = { + .pad = &mali_c55_resizer_pad_ops, +}; + +static int mali_c55_rzr_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer, + sd); + struct v4l2_subdev_krouting routing = { }; + struct v4l2_subdev_route *routes; + unsigned int i; + int ret; + + routes = kcalloc(rzr->num_routes, sizeof(*routes), GFP_KERNEL); + if (!routes) + return -ENOMEM; + + for (i = 0; i < rzr->num_routes; ++i) { + struct v4l2_subdev_route *route = &routes[i]; + + route->sink_pad = i + ? MALI_C55_RZR_SINK_BYPASS_PAD + : MALI_C55_RZR_SINK_PAD; + route->source_pad = MALI_C55_RZR_SOURCE_PAD; + if (route->sink_pad != MALI_C55_RZR_SINK_BYPASS_PAD) + route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + } + + routing.num_routes = rzr->num_routes; + routing.routes = routes; + + ret = __mali_c55_rzr_set_routing(sd, state, &routing); + kfree(routes); + + return ret; +} + +static const struct v4l2_subdev_internal_ops mali_c55_resizer_internal_ops = { + .init_state = mali_c55_rzr_init_state, +}; + +static void mali_c55_resizer_program_coefficients(struct mali_c55 *mali_c55, + unsigned int index) +{ + unsigned int scaler_filt_coefmem_addrs[][2] = {This should be const.+ [MALI_C55_RZR_FR] = { + 0x034A8, /* hfilt */ + 0x044A8 /* vfilt */ + }, + [MALI_C55_RZR_DS] = { + 0x014A8, /* hfilt */ + 0x024A8 /* vfilt */ + }, + }; + unsigned int haddr = scaler_filt_coefmem_addrs[index][0]; + unsigned int vaddr = scaler_filt_coefmem_addrs[index][1]; + unsigned int i, j; + + for (i = 0; i < MALI_C55_RESIZER_COEFS_NUM_BANKS; i++) { + for (j = 0; j < MALI_C55_RESIZER_COEFS_NUM_ENTRIES; j++) { + mali_c55_write(mali_c55, haddr, + mali_c55_scaler_h_filter_coefficients[i][j]); + mali_c55_write(mali_c55, vaddr, + mali_c55_scaler_v_filter_coefficients[i][j]); + + haddr += 4; + vaddr += 4;sizeof(u32) ? Up to you.
I think I'll keep it if it's all the same to you
+ } + } +} + +int mali_c55_register_resizers(struct mali_c55 *mali_c55) +{ + unsigned int i; + int ret; + + for (i = 0; i < MALI_C55_NUM_RZRS; ++i) { + struct mali_c55_resizer *rzr = &mali_c55->resizers[i]; + struct v4l2_subdev *sd = &rzr->sd; + unsigned int num_pads = MALI_C55_RZR_NUM_PADS; + + rzr->id = i; + rzr->streaming = false; + + if (rzr->id == MALI_C55_RZR_FR) + rzr->cap_dev = &mali_c55->cap_devs[MALI_C55_CAP_DEV_FR]; + else + rzr->cap_dev = &mali_c55->cap_devs[MALI_C55_CAP_DEV_DS]; + + mali_c55_resizer_program_coefficients(mali_c55, i); + + v4l2_subdev_init(sd, &mali_c55_resizer_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE + | V4L2_SUBDEV_FL_STREAMS;"|" should be aligned with beginning of the rvalue, i.e. "V" of V4L2_SUBDEV_FL_HAS_DEVNODE.+ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + sd->owner = THIS_MODULE; + sd->internal_ops = &mali_c55_resizer_internal_ops; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s %s", + MALI_C55_DRIVER_NAME, mali_c55_resizer_names[i]); + + rzr->pads[MALI_C55_RZR_SINK_PAD].flags = MEDIA_PAD_FL_SINK; + rzr->pads[MALI_C55_RZR_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE; + + /* Only the FR pipe has a bypass pad. */ + if (rzr->id == MALI_C55_RZR_FR) { + rzr->pads[MALI_C55_RZR_SINK_BYPASS_PAD].flags = + MEDIA_PAD_FL_SINK; + rzr->num_routes = 2; + } else { + num_pads -= 1; + rzr->num_routes = 1; + } + + ret = media_entity_pads_init(&sd->entity, num_pads, rzr->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_cleanup; + + ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd); + if (ret) + goto err_cleanup; + + rzr->mali_c55 = mali_c55; + } + + return 0; + +err_cleanup: + for (; i >= 0; --i) { + struct mali_c55_resizer *rzr = &mali_c55->resizers[i]; + struct v4l2_subdev *sd = &rzr->sd; + + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + } + + return ret; +} + +void mali_c55_unregister_resizers(struct mali_c55 *mali_c55) +{ + unsigned int i; + + for (i = 0; i < MALI_C55_NUM_RZRS; i++) { + struct mali_c55_resizer *resizer = &mali_c55->resizers[i]; + + if (!resizer->mali_c55) + continue; + + v4l2_device_unregister_subdev(&resizer->sd); + v4l2_subdev_cleanup(&resizer->sd); + media_entity_cleanup(&resizer->sd.entity); + } +} diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c new file mode 100644 index 000000000000..042851a4b42d --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - Test pattern generator + * + * Copyright (C) 2024 Ideas on Board Oy + */ + +#include <linux/minmax.h> +#include <linux/string.h> + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "mali-c55-common.h" +#include "mali-c55-registers.h" + +#define MALI_C55_TPG_SRC_PAD 0 +#define MALI_C55_TPG_FIXED_HBLANK 0x20 +#define MALI_C55_TPG_MAX_VBLANK 0xFFFF +#define MALI_C55_TPG_PIXEL_RATE 100000000 + +static const char * const mali_c55_tpg_test_pattern_menu[] = { + "Flat field", + "Horizontal gradient", + "Vertical gradient", + "Vertical bars", + "Arbitrary rectangle", + "White frame on black field" +}; + +static const u32 mali_c55_tpg_mbus_codes[] = { + MEDIA_BUS_FMT_SRGGB16_1X16, + /* + * This is a lie. In RGB mode the Test Pattern Generator actually output + * 16-bits-per-colour data. However, RGB data follows one of the Bypass + * paths which has a 12-bit limit at the insertion point, meaning it + * would be truncated there to match the internal 12-bit format that + * would be output from the debayering block. The same is true of RGB + * data output by a sensor and streamed to the ISP's input port, however + * in that case the ISP's input port requires that data be converted to + * a 20-bit MSB aligned format. Given: + * + * 1. Our chosen topology represents the TPG as a subdevice + * linked to the ISP's input port. + * 2. We need to restrict the ISP's sink pad to only accepting that + * 20-bit RGB format from sensors / CSI-2 receivers. + * 3. All the data ultimately ends up in the same format anyway and + * these data from the TPG are purely internal to the ISP + * + * It seems best to reduce the programming complexity by simply + * pretending that the TPG outputs data in the same format that the ISP + * input port requires, even though it doesn't really. + */ + MEDIA_BUS_FMT_RGB202020_1X60, +}; + +static void __mali_c55_tpg_calc_vblank(struct v4l2_mbus_framefmt *format, + int *def_vblank, int *min_vblank) +{ + unsigned int hts; + int tgt_fps; + int vblank; + + hts = format->width + MALI_C55_TPG_FIXED_HBLANK; + + /* + * The ISP has minimum vertical blanking requirements that must be + * adhered to by the TPG. The minimum is a function of the Iridix blocks + * clocking requirements and the width of the image and horizontal + * blanking, but if we assume the worst case iVariance and sVariance + * values then it boils down to the below. + */ + *min_vblank = 15 + (120500 / hts); + + /* + * We need to set a sensible default vblank for whatever format height + * we happen to be given from set_fmt(). This function just targets + * an even multiple of 15fps. If we can't get 15fps, let's target 5fps. + * If we can't get 5fps we'll take whatever the minimum vblank gives us. + */ + tgt_fps = MALI_C55_TPG_PIXEL_RATE / hts / (format->height + *min_vblank); + + if (tgt_fps < 5) + vblank = *min_vblank; + else + vblank = MALI_C55_TPG_PIXEL_RATE / hts + / max(rounddown(tgt_fps, 15), 5); + + *def_vblank = ALIGN_DOWN(vblank, 2) - format->height; +} + +static int mali_c55_tpg_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mali_c55_tpg *tpg = container_of(ctrl->handler, + struct mali_c55_tpg, + ctrls.handler); + struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg); + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + mali_c55_write(mali_c55, MALI_C55_REG_TEST_GEN_CH0_PATTERN_TYPE, + ctrl->val); + break; + case V4L2_CID_VBLANK: + mali_c55_update_bits(mali_c55, MALI_C55_REG_BLANKING, + MALI_C55_REG_VBLANK_MASK, ctrl->val); + break; + default: + dev_err(mali_c55->dev, "invalid V4L2 control ID\n"); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops mali_c55_tpg_ctrl_ops = { + .s_ctrl = &mali_c55_tpg_s_ctrl, +}; + +static void mali_c55_tpg_configure(struct mali_c55 *mali_c55, + struct v4l2_subdev *sd) +{ + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + + /* + * hblank needs setting, but is a read-only control and thus won't be + * called during __v4l2_ctrl_handler_setup(). Do it here instead. + */ + mali_c55_update_bits(mali_c55, MALI_C55_REG_BLANKING, + MALI_C55_REG_HBLANK_MASK, + MALI_C55_TPG_FIXED_HBLANK); + mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO, + MALI_C55_REG_GEN_VIDEO_MULTI_MASK, 0x01); + + state = v4l2_subdev_lock_and_get_active_state(sd); + fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD); + + mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0, + MALI_C55_TEST_PATTERN_RGB_MASK, + fmt->code == MEDIA_BUS_FMT_RGB202020_1X60 ? + 0x01 : 0x0); + + v4l2_subdev_unlock_state(state); +} + +static int mali_c55_tpg_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd); + struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg); + + if (!enable) { + mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0, + MALI_C55_TEST_PATTERN_ON_OFF, 0x00); + mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO, + MALI_C55_REG_GEN_VIDEO_ON_MASK, 0x00); + } else { + /* + * One might reasonably expect the framesize to be set here + * given it's configurable in .set_fmt(), but it's done in the + * ISP subdevice's .s_stream() instead, as the same register is + * also used to indicate the size of the data coming from the + * sensor. + */ + mali_c55_tpg_configure(mali_c55, sd); + __v4l2_ctrl_handler_setup(sd->ctrl_handler); + + mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0, + MALI_C55_TEST_PATTERN_ON_OFF, + MALI_C55_TEST_PATTERN_ON_OFF); + mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO, + MALI_C55_REG_GEN_VIDEO_ON_MASK, + MALI_C55_REG_GEN_VIDEO_ON_MASK); + } + + return 0; +} + +static const struct v4l2_subdev_video_ops mali_c55_tpg_video_ops = { + .s_stream = &mali_c55_tpg_s_stream, +}; + +static int mali_c55_tpg_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad >= sd->entity.num_pads) + return -EINVAL;This check is done by the framework, you can drop it here.+ + if (code->index >= ARRAY_SIZE(mali_c55_tpg_mbus_codes)) + return -EINVAL; + + code->code = mali_c55_tpg_mbus_codes[code->index]; + + return 0; +} + +static int mali_c55_tpg_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index > 0 || fse->pad > sd->entity.num_pads) + return -EINVAL; + + fse->min_width = MALI_C55_MIN_WIDTH; + fse->max_width = MALI_C55_MAX_WIDTH; + fse->min_height = MALI_C55_MIN_HEIGHT; + fse->max_height = MALI_C55_MAX_HEIGHT; + + return 0; +} + +static int mali_c55_tpg_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd); + struct v4l2_mbus_framefmt *fmt = &format->format; + int vblank_def, vblank_min; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) { + if (fmt->code == mali_c55_tpg_mbus_codes[i]) + break; + } + + if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes)) + fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16; + + /* + * The TPG says that the test frame timing generation logic expects a + * minimum framesize of 4x4 pixels, but given the rest of the ISP can't + * handle anything smaller than 128x128 it seems pointless to allow a + * smaller frame. + */ + clamp_t(unsigned int, fmt->width, MALI_C55_MIN_WIDTH, + MALI_C55_MAX_WIDTH); + clamp_t(unsigned int, fmt->height, MALI_C55_MIN_HEIGHT, + MALI_C55_MAX_HEIGHT); + + *v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD) = *fmt; +Shouldn't the controls below only be changed for the active format?
Yes! Thank you
+ __mali_c55_tpg_calc_vblank(fmt, &vblank_def, &vblank_min); + __v4l2_ctrl_modify_range(tpg->ctrls.vblank, vblank_min, + MALI_C55_TPG_MAX_VBLANK, 1, vblank_def); + __v4l2_ctrl_s_ctrl(tpg->ctrls.vblank, vblank_def); + + return 0; +} + +static const struct v4l2_subdev_pad_ops mali_c55_tpg_pad_ops = { + .enum_mbus_code = mali_c55_tpg_enum_mbus_code, + .enum_frame_size = mali_c55_tpg_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mali_c55_tpg_set_fmt, +}; + +static const struct v4l2_subdev_ops mali_c55_tpg_ops = { + .video = &mali_c55_tpg_video_ops, + .pad = &mali_c55_tpg_pad_ops, +}; + +static int mali_c55_tpg_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, MALI_C55_TPG_SRC_PAD);Can be assigned in the declaration.
How would you make it fit that way?
+ + fmt->width = MALI_C55_DEFAULT_WIDTH; + fmt->height = MALI_C55_DEFAULT_HEIGHT; + fmt->field = V4L2_FIELD_NONE; + fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16; + + return 0; +} + +static const struct v4l2_subdev_internal_ops mali_c55_tpg_internal_ops = { + .init_state = mali_c55_tpg_init_state, +}; + +static int mali_c55_tpg_init_controls(struct mali_c55 *mali_c55) +{ + struct mali_c55_tpg_ctrls *ctrls = &mali_c55->tpg.ctrls; + struct v4l2_subdev *sd = &mali_c55->tpg.sd; + struct v4l2_mbus_framefmt *format; + struct v4l2_subdev_state *state; + int vblank_def, vblank_min; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(sd); + format = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD); + + ret = v4l2_ctrl_handler_init(&ctrls->handler, 1); + if (ret) + goto err_unlock; + + ctrls->test_pattern = v4l2_ctrl_new_std_menu_items(&ctrls->handler, + &mali_c55_tpg_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(mali_c55_tpg_test_pattern_menu) - 1, + 0, 3, mali_c55_tpg_test_pattern_menu); + + /* + * We fix hblank at the minimum allowed value and control framerate + * solely through the vblank control. + */ + ctrls->hblank = v4l2_ctrl_new_std(&ctrls->handler, + &mali_c55_tpg_ctrl_ops, + V4L2_CID_HBLANK, MALI_C55_TPG_FIXED_HBLANK, + MALI_C55_TPG_FIXED_HBLANK, 1, + MALI_C55_TPG_FIXED_HBLANK); + if (ctrls->hblank) + ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + __mali_c55_tpg_calc_vblank(format, &vblank_def, &vblank_min); + ctrls->vblank = v4l2_ctrl_new_std(&ctrls->handler, + &mali_c55_tpg_ctrl_ops, + V4L2_CID_VBLANK, vblank_min, + MALI_C55_TPG_MAX_VBLANK, 1, + vblank_def); + + if (ctrls->handler.error) { + dev_err(mali_c55->dev, "Error during v4l2 controls init\n"); + ret = ctrls->handler.error; + goto err_free_handler; + } + + ctrls->handler.lock = &mali_c55->tpg.lock; + mali_c55->tpg.sd.ctrl_handler = &ctrls->handler; + + v4l2_subdev_unlock_state(state); + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(&ctrls->handler); +err_unlock: + v4l2_subdev_unlock_state(state); + return ret; +} + +int mali_c55_register_tpg(struct mali_c55 *mali_c55) +{ + struct mali_c55_tpg *tpg = &mali_c55->tpg; + struct v4l2_subdev *sd = &tpg->sd; + struct media_pad *pad = &tpg->pad; + int ret; + + mutex_init(&tpg->lock); + + v4l2_subdev_init(sd, &mali_c55_tpg_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + sd->owner = THIS_MODULE; + sd->internal_ops = &mali_c55_tpg_internal_ops; + strscpy(sd->name, MALI_C55_DRIVER_NAME " tpg", sizeof(sd->name)); + + pad->flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT; + ret = media_entity_pads_init(&sd->entity, 1, pad); + if (ret) { + dev_err(mali_c55->dev, + "Failed to initialize media entity pads\n"); + goto err_destroy_mutex; + } + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_cleanup_media_entity; + + ret = mali_c55_tpg_init_controls(mali_c55); + if (ret) { + dev_err(mali_c55->dev, + "Error initialising controls\n"); + goto err_cleanup_subdev; + } + + ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd); + if (ret) { + dev_err(mali_c55->dev, "Failed to register tpg subdev\n"); + goto err_free_ctrl_handler; + } + + /* + * By default the colour settings lead to a very dim image that is + * nearly indistinguishable from black on some monitor settings. Ramp + * them up a bit so the image is brighter. + */ + mali_c55_write(mali_c55, MALI_C55_REG_TPG_R_BACKGROUND, + MALI_C55_TPG_BACKGROUND_MAX); + mali_c55_write(mali_c55, MALI_C55_REG_TPG_G_BACKGROUND, + MALI_C55_TPG_BACKGROUND_MAX); + mali_c55_write(mali_c55, MALI_C55_REG_TPG_B_BACKGROUND, + MALI_C55_TPG_BACKGROUND_MAX); + + tpg->mali_c55 = mali_c55; + + return 0; + +err_free_ctrl_handler: + v4l2_ctrl_handler_free(&tpg->ctrls.handler); +err_cleanup_subdev: + v4l2_subdev_cleanup(sd); +err_cleanup_media_entity: + media_entity_cleanup(&sd->entity); +err_destroy_mutex: + mutex_destroy(&tpg->lock); + + return ret; +} + +void mali_c55_unregister_tpg(struct mali_c55 *mali_c55) +{ + struct mali_c55_tpg *tpg = &mali_c55->tpg; + + if (!tpg->mali_c55) + return; + + v4l2_device_unregister_subdev(&tpg->sd); + v4l2_subdev_cleanup(&tpg->sd); + media_entity_cleanup(&tpg->sd.entity); + v4l2_ctrl_handler_free(&tpg->ctrls.handler); + mutex_destroy(&tpg->lock); +}