/* * Copyright (C) ST-Ericsson SA 2010 * * ST-Ericsson B2R2 node splitter * * Author: Fredrik Allansson * for ST-Ericsson. * * License terms: GNU General Public License (GPL), version 2. */ #include "b2r2_debug.h" #include "b2r2_node_split.h" #include "b2r2_internal.h" #include "b2r2_hw.h" #include "b2r2_filters.h" #include /* * Macros and constants */ #define ABS(x) ((x) < 0 ? -(x) : (x)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define INSTANCES_DEFAULT_SIZE 10 #define INSTANCES_GROW_SIZE 5 /* * Internal types */ /* * Global variables */ /** * VMX values for different color space conversions */ static const u32 vmx_rgb_to_yuv[] = { B2R2_VMX0_RGB_TO_YUV_601_VIDEO, B2R2_VMX1_RGB_TO_YUV_601_VIDEO, B2R2_VMX2_RGB_TO_YUV_601_VIDEO, B2R2_VMX3_RGB_TO_YUV_601_VIDEO, }; static const u32 vmx_rgb_to_blt_yuv888[] = { B2R2_VMX0_RGB_TO_BLT_YUV888_601_VIDEO, B2R2_VMX1_RGB_TO_BLT_YUV888_601_VIDEO, B2R2_VMX2_RGB_TO_BLT_YUV888_601_VIDEO, B2R2_VMX3_RGB_TO_BLT_YUV888_601_VIDEO, }; static const u32 vmx_yuv_to_rgb[] = { B2R2_VMX0_YUV_TO_RGB_601_VIDEO, B2R2_VMX1_YUV_TO_RGB_601_VIDEO, B2R2_VMX2_YUV_TO_RGB_601_VIDEO, B2R2_VMX3_YUV_TO_RGB_601_VIDEO, }; static const u32 vmx_blt_yuv888_to_rgb[] = { B2R2_VMX0_BLT_YUV888_TO_RGB_601_VIDEO, B2R2_VMX1_BLT_YUV888_TO_RGB_601_VIDEO, B2R2_VMX2_BLT_YUV888_TO_RGB_601_VIDEO, B2R2_VMX3_BLT_YUV888_TO_RGB_601_VIDEO, }; static const u32 vmx_yuv_to_blt_yuv888[] = { B2R2_VMX0_YUV_TO_BLT_YUV888, B2R2_VMX1_YUV_TO_BLT_YUV888, B2R2_VMX2_YUV_TO_BLT_YUV888, B2R2_VMX3_YUV_TO_BLT_YUV888, }; static const u32 vmx_blt_yuv888_to_yuv[] = { B2R2_VMX0_BLT_YUV888_TO_YUV, B2R2_VMX1_BLT_YUV888_TO_YUV, B2R2_VMX2_BLT_YUV888_TO_YUV, B2R2_VMX3_BLT_YUV888_TO_YUV, }; static const u32 vmx_yvu_to_blt_yuv888[] = { B2R2_VMX0_YVU_TO_BLT_YUV888, B2R2_VMX1_YVU_TO_BLT_YUV888, B2R2_VMX2_YVU_TO_BLT_YUV888, B2R2_VMX3_YVU_TO_BLT_YUV888, }; static const u32 vmx_blt_yuv888_to_yvu[] = { B2R2_VMX0_BLT_YUV888_TO_YVU, B2R2_VMX1_BLT_YUV888_TO_YVU, B2R2_VMX2_BLT_YUV888_TO_YVU, B2R2_VMX3_BLT_YUV888_TO_YVU, }; static const u32 vmx_yvu_to_rgb[] = { B2R2_VMX0_YVU_TO_RGB_601_VIDEO, B2R2_VMX1_YVU_TO_RGB_601_VIDEO, B2R2_VMX2_YVU_TO_RGB_601_VIDEO, B2R2_VMX3_YVU_TO_RGB_601_VIDEO, }; static const u32 vmx_rgb_to_yvu[] = { B2R2_VMX0_RGB_TO_YVU_601_VIDEO, B2R2_VMX1_RGB_TO_YVU_601_VIDEO, B2R2_VMX2_RGB_TO_YVU_601_VIDEO, B2R2_VMX3_RGB_TO_YVU_601_VIDEO, }; static const u32 vmx_rgb_to_bgr[] = { B2R2_VMX0_RGB_TO_BGR, B2R2_VMX1_RGB_TO_BGR, B2R2_VMX2_RGB_TO_BGR, B2R2_VMX3_RGB_TO_BGR, }; static const u32 vmx_bgr_to_yuv[] = { B2R2_VMX0_BGR_TO_YUV_601_VIDEO, B2R2_VMX1_BGR_TO_YUV_601_VIDEO, B2R2_VMX2_BGR_TO_YUV_601_VIDEO, B2R2_VMX3_BGR_TO_YUV_601_VIDEO, }; static const u32 vmx_yuv_to_bgr[] = { B2R2_VMX0_YUV_TO_BGR_601_VIDEO, B2R2_VMX1_YUV_TO_BGR_601_VIDEO, B2R2_VMX2_YUV_TO_BGR_601_VIDEO, B2R2_VMX3_YUV_TO_BGR_601_VIDEO, }; static const u32 vmx_bgr_to_yvu[] = { B2R2_VMX0_BGR_TO_YVU_601_VIDEO, B2R2_VMX1_BGR_TO_YVU_601_VIDEO, B2R2_VMX2_BGR_TO_YVU_601_VIDEO, B2R2_VMX3_BGR_TO_YVU_601_VIDEO, }; static const u32 vmx_yvu_to_bgr[] = { B2R2_VMX0_YVU_TO_BGR_601_VIDEO, B2R2_VMX1_YVU_TO_BGR_601_VIDEO, B2R2_VMX2_YVU_TO_BGR_601_VIDEO, B2R2_VMX3_YVU_TO_BGR_601_VIDEO, }; static const u32 vmx_yvu_to_yuv[] = { B2R2_VMX0_YVU_TO_YUV_601_VIDEO, B2R2_VMX1_YVU_TO_YUV_601_VIDEO, B2R2_VMX2_YVU_TO_YUV_601_VIDEO, B2R2_VMX3_YVU_TO_YUV_601_VIDEO, }; /* * Forward declaration of private functions */ static int analyze_fmt_conv(struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, const u32 **vmx, u32 *node_count); static int analyze_color_fill(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count); static int analyze_copy(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count); static int analyze_scaling(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count); static int analyze_rotate(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count); static int analyze_transform(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count); static int analyze_rot_scale(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count); static int analyze_scale_factors(struct b2r2_node_split_job *this); static void configure_src(struct b2r2_node *node, struct b2r2_node_split_buf *src, const u32 *ivmx); static void configure_bg(struct b2r2_node *node, struct b2r2_node_split_buf *bg, bool swap_fg_bg); static int configure_dst(struct b2r2_node *node, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next); static void configure_blend(struct b2r2_node *node, u32 flags, u32 global_alpha); static void configure_clip(struct b2r2_node *node, struct b2r2_blt_rect *clip_rect); static int configure_tile(struct b2r2_node_split_job *this, struct b2r2_node *node, struct b2r2_node **next); static void configure_direct_fill(struct b2r2_node *node, u32 color, struct b2r2_node_split_buf *dst, struct b2r2_node **next); static int configure_fill(struct b2r2_node *node, u32 color, enum b2r2_blt_fmt fmt, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next); static void configure_direct_copy(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, struct b2r2_node **next); static int configure_copy(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next, struct b2r2_node_split_job *this); static int configure_rotate(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next, struct b2r2_node_split_job *this); static int configure_scale(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, u16 h_rsf, u16 v_rsf, const u32 *ivmx, struct b2r2_node **next, struct b2r2_node_split_job *this); static int configure_rot_scale(struct b2r2_node_split_job *this, struct b2r2_node *node, struct b2r2_node **next); static int check_rect(const struct b2r2_blt_img *img, const struct b2r2_blt_rect *rect, const struct b2r2_blt_rect *clip); static void set_buf(struct b2r2_node_split_buf *buf, u32 addr, const struct b2r2_blt_img *img, const struct b2r2_blt_rect *rect, bool color_fill, u32 color); static int setup_tmp_buf(struct b2r2_node_split_buf *this, u32 max_size, enum b2r2_blt_fmt pref_fmt, u32 pref_width, u32 pref_height); static enum b2r2_ty get_alpha_range(enum b2r2_blt_fmt fmt); static u32 set_alpha(enum b2r2_blt_fmt fmt, u8 alpha, u32 color); static u8 get_alpha(enum b2r2_blt_fmt fmt, u32 pixel); static bool fmt_has_alpha(enum b2r2_blt_fmt fmt); static bool is_rgb_fmt(enum b2r2_blt_fmt fmt); static bool is_bgr_fmt(enum b2r2_blt_fmt fmt); static bool is_yuv_fmt(enum b2r2_blt_fmt fmt); static bool is_yvu_fmt(enum b2r2_blt_fmt fmt); static bool is_yuv420_fmt(enum b2r2_blt_fmt fmt); static bool is_yuv422_fmt(enum b2r2_blt_fmt fmt); static bool is_yuv444_fmt(enum b2r2_blt_fmt fmt); static bool is_yvu420_fmt(enum b2r2_blt_fmt fmt); static bool is_yvu422_fmt(enum b2r2_blt_fmt fmt); static int fmt_byte_pitch(enum b2r2_blt_fmt fmt, u32 width); static enum b2r2_native_fmt to_native_fmt(enum b2r2_blt_fmt fmt); static u32 to_RGB888(u32 color, const enum b2r2_blt_fmt fmt); static enum b2r2_fmt_type get_fmt_type(enum b2r2_blt_fmt fmt); static bool is_transform(const struct b2r2_blt_request *req); static int calculate_scale_factor(u32 from, u32 to, u16 *sf_out); static s32 rescale(s32 dim, u16 sf); static s32 inv_rescale(s32 dim, u16 sf); static void set_target(struct b2r2_node *node, u32 addr, struct b2r2_node_split_buf *buf); static void set_src(struct b2r2_src_config *src, u32 addr, struct b2r2_node_split_buf *buf); static void set_src_1(struct b2r2_node *node, u32 addr, struct b2r2_node_split_buf *buf); static void set_src_2(struct b2r2_node *node, u32 addr, struct b2r2_node_split_buf *buf); static void set_src_3(struct b2r2_node *node, u32 addr, struct b2r2_node_split_buf *buf); static void set_ivmx(struct b2r2_node *node, const u32 *vmx_values); static void reset_nodes(struct b2r2_node *node); static bool bg_format_require_ivmx(enum b2r2_blt_fmt bg_fmt, enum b2r2_blt_fmt dst_fmt); /* * Public functions */ /** * b2r2_node_split_analyze() - analyzes the request */ int b2r2_node_split_analyze(const struct b2r2_blt_request *req, u32 max_buf_size, u32 *node_count, struct b2r2_work_buf **bufs, u32 *buf_count, struct b2r2_node_split_job *this) { int ret; bool color_fill; b2r2_log_info("%s\n", __func__); memset(this, 0, sizeof(*this)); /* Copy parameters */ this->flags = req->user_req.flags; this->transform = req->user_req.transform; this->max_buf_size = max_buf_size; this->global_alpha = req->user_req.global_alpha; this->buf_count = 0; this->node_count = 0; if (this->flags & B2R2_BLT_FLAG_BLUR) { ret = -ENOSYS; goto unsupported; } /* Unsupported formats on src */ switch (req->user_req.src_img.fmt) { case B2R2_BLT_FMT_24_BIT_YUV888: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_24_BIT_VUY888: case B2R2_BLT_FMT_32_BIT_VUYA8888: if (is_bgr_fmt(req->user_req.dst_img.fmt)) { ret = -ENOSYS; goto unsupported; } break; default: break; } /* Unsupported formats on dst */ switch (req->user_req.dst_img.fmt) { case B2R2_BLT_FMT_24_BIT_YUV888: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_24_BIT_VUY888: case B2R2_BLT_FMT_32_BIT_VUYA8888: if (is_bgr_fmt(req->user_req.src_img.fmt)) { ret = -ENOSYS; goto unsupported; } break; default: break; } /* Unsupported formats on bg */ if (this->flags & B2R2_BLT_FLAG_BG_BLEND) { /* * There are no ivmx on source 1, so check that * there is no such requirement on the background * to destination format conversion. This check is sufficient * since the node splitter currently does not support * destination ivmx. That fact also removes * the source format as a parameter when checking the * background format. */ if (bg_format_require_ivmx(req->user_req.bg_img.fmt, req->user_req.dst_img.fmt)) { ret = -ENOSYS; goto unsupported; } } if ((this->flags & B2R2_BLT_FLAG_SOURCE_COLOR_KEY) && (is_yuv_fmt(req->user_req.src_img.fmt) || req->user_req.src_img.fmt == B2R2_BLT_FMT_1_BIT_A1 || req->user_req.src_img.fmt == B2R2_BLT_FMT_8_BIT_A8)) { b2r2_log_warn("%s: Unsupported: source color keying with " "YUV or pure alpha formats.\n", __func__); ret = -ENOSYS; goto unsupported; } if (this->flags & (B2R2_BLT_FLAG_DEST_COLOR_KEY | B2R2_BLT_FLAG_SOURCE_MASK)) { b2r2_log_warn("%s: Unsupported: source mask, " "destination color keying.\n", __func__); ret = -ENOSYS; goto unsupported; } if ((req->user_req.flags & B2R2_BLT_FLAG_CLUT_COLOR_CORRECTION) && req->user_req.clut == NULL) { b2r2_log_warn("%s: Invalid request: no table specified " "for CLUT color correction.\n", __func__); return -EINVAL; } /* Check for color fill */ color_fill = (this->flags & (B2R2_BLT_FLAG_SOURCE_FILL | B2R2_BLT_FLAG_SOURCE_FILL_RAW)) != 0; /* Configure the source and destination buffers */ set_buf(&this->src, req->src_resolved.physical_address, &req->user_req.src_img, &req->user_req.src_rect, color_fill, req->user_req.src_color); if (this->flags & B2R2_BLT_FLAG_BG_BLEND) { set_buf(&this->bg, req->bg_resolved.physical_address, &req->user_req.bg_img, &req->user_req.bg_rect, false, 0); } set_buf(&this->dst, req->dst_resolved.physical_address, &req->user_req.dst_img, &req->user_req.dst_rect, false, 0); b2r2_log_info("%s:\n" "\t\tsrc.rect=(%4d, %4d, %4d, %4d)\t" "bg.rect=(%4d, %4d, %4d, %4d)\t" "dst.rect=(%4d, %4d, %4d, %4d)\n", __func__, this->src.rect.x, this->src.rect.y, this->src.rect.width, this->src.rect.height, this->bg.rect.x, this->bg.rect.y, this->bg.rect.width, this->bg.rect.height, this->dst.rect.x, this->dst.rect.y, this->dst.rect.width, this->dst.rect.height); if (this->flags & B2R2_BLT_FLAG_DITHER) this->dst.dither = B2R2_TTY_RGB_ROUND_DITHER; if (this->flags & B2R2_BLT_FLAG_SOURCE_COLOR_KEY) this->flag_param = req->user_req.src_color; /* Check for blending */ if ((this->flags & B2R2_BLT_FLAG_GLOBAL_ALPHA_BLEND) && (this->global_alpha != 255)) this->blend = true; else if (this->flags & B2R2_BLT_FLAG_PER_PIXEL_ALPHA_BLEND) this->blend = (color_fill && fmt_has_alpha(this->dst.fmt)) || fmt_has_alpha(this->src.fmt); else if (this->flags & B2R2_BLT_FLAG_BG_BLEND) this->blend = true; if (this->blend && this->src.type == B2R2_FMT_TYPE_PLANAR) { b2r2_log_warn("%s: Unsupported: blend with planar source\n", __func__); ret = -ENOSYS; goto unsupported; } /* Check for clipping */ this->clip = (this->flags & B2R2_BLT_FLAG_DESTINATION_CLIP) != 0; if (this->clip) { s32 l = req->user_req.dst_clip_rect.x; s32 r = l + req->user_req.dst_clip_rect.width; s32 t = req->user_req.dst_clip_rect.y; s32 b = t + req->user_req.dst_clip_rect.height; /* Intersect the clip and buffer rects */ if (l < 0) l = 0; if (r > req->user_req.dst_img.width) r = req->user_req.dst_img.width; if (t < 0) t = 0; if (b > req->user_req.dst_img.height) b = req->user_req.dst_img.height; this->clip_rect.x = l; this->clip_rect.y = t; this->clip_rect.width = r - l; this->clip_rect.height = b - t; } else { /* Set the clip rectangle to the buffer bounds */ this->clip_rect.x = 0; this->clip_rect.y = 0; this->clip_rect.width = req->user_req.dst_img.width; this->clip_rect.height = req->user_req.dst_img.height; } /* Validate the destination */ ret = check_rect(&req->user_req.dst_img, &req->user_req.dst_rect, &this->clip_rect); if (ret < 0) goto error; /* Validate the source (if not color fill) */ if (!color_fill) { ret = check_rect(&req->user_req.src_img, &req->user_req.src_rect, NULL); if (ret < 0) goto error; } /* Validate the background source */ if (this->flags & B2R2_BLT_FLAG_BG_BLEND) { ret = check_rect(&req->user_req.bg_img, &req->user_req.bg_rect, NULL); if (ret < 0) goto error; } /* Do the analysis depending on the type of operation */ if (color_fill) { ret = analyze_color_fill(this, req, &this->node_count); } else { bool upsample; bool downsample; /* * YUV formats that are non-raster, non-yuv444 needs to be * up (or down) sampled using the resizer. * * NOTE: The resizer needs to be enabled for YUV444 as well, * even though there is no upsampling. This is most * likely a bug in the hardware. */ upsample = this->src.type != B2R2_FMT_TYPE_RASTER && is_yuv_fmt(this->src.fmt); downsample = this->dst.type != B2R2_FMT_TYPE_RASTER && is_yuv_fmt(this->dst.fmt); if (is_transform(req) || upsample || downsample) ret = analyze_transform(this, req, &this->node_count, &this->buf_count); else ret = analyze_copy(this, req, &this->node_count, &this->buf_count); } if (ret == -ENOSYS) { goto unsupported; } else if (ret < 0) { b2r2_log_warn("%s: Analysis failed!\n", __func__); goto error; } /* Setup the origin and movement of the destination window */ if (this->dst.hso == B2R2_TY_HSO_RIGHT_TO_LEFT) { this->dst.dx = -this->dst.win.width; this->dst.win.x = this->dst.rect.x + this->dst.rect.width - 1; } else { this->dst.dx = this->dst.win.width; this->dst.win.x = this->dst.rect.x; } if (this->dst.vso == B2R2_TY_VSO_BOTTOM_TO_TOP) { this->dst.dy = -this->dst.win.height; this->dst.win.y = this->dst.rect.y + this->dst.rect.height - 1; } else { this->dst.dy = this->dst.win.height; this->dst.win.y = this->dst.rect.y; } *buf_count = this->buf_count; *node_count = this->node_count; if (this->buf_count > 0) *bufs = &this->work_bufs[0]; b2r2_log_info("%s: dst.win=(%d, %d, %d, %d), dst.dx=%d, dst.dy=%d\n", __func__, this->dst.win.x, this->dst.win.y, this->dst.win.width, this->dst.win.height, this->dst.dx, this->dst.dy); if (this->buf_count > 0) b2r2_log_info("%s: buf_count=%d, buf_size=%d, node_count=%d\n", __func__, *buf_count, bufs[0]->size, *node_count); else b2r2_log_info("%s: buf_count=%d, node_count=%d\n", __func__, *buf_count, *node_count); return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); unsupported: return ret; } /** * b2r2_node_split_configure() - configures the node list */ int b2r2_node_split_configure(struct b2r2_node_split_job *this, struct b2r2_node *first) { int ret; struct b2r2_node_split_buf *dst = &this->dst; struct b2r2_node *node = first; u32 x_pixels = 0; u32 y_pixels = 0; reset_nodes(node); while (y_pixels < dst->rect.height) { s32 dst_x = dst->win.x; s32 dst_w = dst->win.width; /* Clamp window height */ if (dst->win.height > dst->rect.height - y_pixels) dst->win.height = dst->rect.height - y_pixels; while (x_pixels < dst->rect.width) { /* Clamp window width */ if (dst_w > dst->rect.width - x_pixels) dst->win.width = dst->rect.width - x_pixels; ret = configure_tile(this, node, &node); if (ret < 0) goto error; dst->win.x += dst->dx; x_pixels += max(dst->dx, -dst->dx); b2r2_log_info("%s: x_pixels=%d\n", __func__, x_pixels); } dst->win.y += dst->dy; y_pixels += max(dst->dy, -dst->dy); dst->win.x = dst_x; dst->win.width = dst_w; x_pixels = 0; b2r2_log_info("%s: y_pixels=%d\n", __func__, y_pixels); } return 0; error: b2r2_log_warn("%s: error!\n", __func__); return ret; } /** * b2r2_node_split_assign_buffers() - assigns temporary buffers to the node list */ int b2r2_node_split_assign_buffers(struct b2r2_node_split_job *this, struct b2r2_node *first, struct b2r2_work_buf *bufs, u32 buf_count) { struct b2r2_node *node = first; while (node != NULL) { /* The indices are offset by one */ if (node->dst_tmp_index) { BUG_ON(node->dst_tmp_index > buf_count); b2r2_log_info("%s: assigning buf %d as dst\n", __func__, node->dst_tmp_index); node->node.GROUP1.B2R2_TBA = bufs[node->dst_tmp_index - 1].phys_addr; } if (node->src_tmp_index) { u32 addr = bufs[node->src_tmp_index - 1].phys_addr; b2r2_log_info("%s: " "assigning buf %d as src %d ", __func__, node->src_tmp_index, node->src_index); BUG_ON(node->src_tmp_index > buf_count); switch (node->src_index) { case 1: b2r2_log_info("1\n"); node->node.GROUP3.B2R2_SBA = addr; break; case 2: b2r2_log_info("2\n"); node->node.GROUP4.B2R2_SBA = addr; break; case 3: b2r2_log_info("3\n"); node->node.GROUP5.B2R2_SBA = addr; break; default: BUG_ON(1); break; } } b2r2_log_info("%s: tba=%p\tsba=%p\n", __func__, (void *)node->node.GROUP1.B2R2_TBA, (void *)node->node.GROUP4.B2R2_SBA); node = node->next; } return 0; } /** * b2r2_node_split_unassign_buffers() - releases temporary buffers */ void b2r2_node_split_unassign_buffers(struct b2r2_node_split_job *this, struct b2r2_node *first) { return; } /** * b2r2_node_split_cancel() - cancels and releases a job instance */ void b2r2_node_split_cancel(struct b2r2_node_split_job *this) { memset(this, 0, sizeof(*this)); return; } /* * Private functions */ static int check_rect(const struct b2r2_blt_img *img, const struct b2r2_blt_rect *rect, const struct b2r2_blt_rect *clip) { int ret; s32 l, r, b, t; /* Check rectangle dimensions*/ if ((rect->width <= 0) || (rect->height <= 0)) { b2r2_log_warn("%s: " "Illegal rect (%d, %d, %d, %d)\n", __func__, rect->x, rect->y, rect->width, rect->height); ret = -EINVAL; goto error; } /* If we are using clip we should only look at the intersection of the rects */ if (clip) { l = MAX(rect->x, clip->x); t = MAX(rect->y, clip->y); r = MIN(rect->x + rect->width, clip->x + clip->width); b = MIN(rect->y + rect->height, clip->y + clip->height); } else { l = rect->x; t = rect->y; r = rect->x + rect->width; b = rect->y + rect->height; } /* Check so that the rect isn't outside the buffer */ if ((l < 0) || (t < 0) || (l >= img->width) || (t >= img->height)) { b2r2_log_warn("%s: " "rect origin outside buffer\n", __func__); ret = -EINVAL; goto error; } if ((r > img->width) || (b > img->height)) { b2r2_log_warn("%s: " "rect ends outside buffer\n", __func__); ret = -EINVAL; goto error; } /* Check so the intersected rectangle isn't empty */ if ((l == r) || (t == b)) { b2r2_log_warn("%s: " "rect is empty (width or height zero)\n", __func__); ret = -EINVAL; goto error; } return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * bg_format_require_ivmx() * * Check if there are any color space conversion needed for the * background to the destination format. */ static bool bg_format_require_ivmx(enum b2r2_blt_fmt bg_fmt, enum b2r2_blt_fmt dst_fmt) { if (is_rgb_fmt(bg_fmt)) { if (is_yvu_fmt(dst_fmt)) return true; else if (dst_fmt == B2R2_BLT_FMT_24_BIT_YUV888 || dst_fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || dst_fmt == B2R2_BLT_FMT_24_BIT_VUY888 || dst_fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) return true; else if (is_yuv_fmt(dst_fmt)) return true; else if (is_bgr_fmt(dst_fmt)) return true; } else if (is_yvu_fmt(bg_fmt)) { if (is_rgb_fmt(dst_fmt)) return true; else if (is_bgr_fmt(dst_fmt)) return true; else if (dst_fmt == B2R2_BLT_FMT_24_BIT_YUV888 || dst_fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || dst_fmt == B2R2_BLT_FMT_24_BIT_VUY888 || dst_fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) return true; else if (is_yuv_fmt(dst_fmt) && !is_yvu_fmt(dst_fmt)) return true; } else if (bg_fmt == B2R2_BLT_FMT_24_BIT_YUV888 || bg_fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || bg_fmt == B2R2_BLT_FMT_24_BIT_VUY888 || bg_fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) { if (is_rgb_fmt(dst_fmt)) { return true; } else if (is_yvu_fmt(dst_fmt)) { return true; } else if (is_yuv_fmt(dst_fmt)) { switch (dst_fmt) { case B2R2_BLT_FMT_24_BIT_YUV888: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_24_BIT_VUY888: case B2R2_BLT_FMT_32_BIT_VUYA8888: break; default: return true; } } } else if (is_yuv_fmt(bg_fmt)) { if (is_rgb_fmt(dst_fmt)) return true; else if (is_bgr_fmt(dst_fmt)) return true; else if (dst_fmt == B2R2_BLT_FMT_24_BIT_YUV888 || dst_fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || dst_fmt == B2R2_BLT_FMT_24_BIT_VUY888 || dst_fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) return true; else if (is_yvu_fmt(dst_fmt)) return true; } else if (is_bgr_fmt(bg_fmt)) { if (is_rgb_fmt(dst_fmt)) return true; else if (is_yvu_fmt(dst_fmt)) return true; else if (dst_fmt == B2R2_BLT_FMT_24_BIT_YUV888 || dst_fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || dst_fmt == B2R2_BLT_FMT_24_BIT_VUY888 || dst_fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) return true; else if (is_yuv_fmt(dst_fmt)) return true; } return false; } /** * analyze_fmt_conv() - analyze the format conversions needed for a job */ static int analyze_fmt_conv(struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, const u32 **vmx, u32 *node_count) { if (is_rgb_fmt(src->fmt)) { if (is_yvu_fmt(dst->fmt)) *vmx = &vmx_rgb_to_yvu[0]; else if (dst->fmt == B2R2_BLT_FMT_24_BIT_YUV888 || dst->fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || dst->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || dst->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) /* * (A)YUV/VUY(A) formats differ only in component * order. This is handled by the endianness bit * in B2R2_STY/TTY registers when src/target are set. */ *vmx = &vmx_rgb_to_blt_yuv888[0]; else if (is_yuv_fmt(dst->fmt)) *vmx = &vmx_rgb_to_yuv[0]; else if (is_bgr_fmt(dst->fmt)) *vmx = &vmx_rgb_to_bgr[0]; } else if (is_yvu_fmt(src->fmt)) { if (is_rgb_fmt(dst->fmt)) *vmx = &vmx_yvu_to_rgb[0]; else if (is_bgr_fmt(dst->fmt)) *vmx = &vmx_yvu_to_bgr[0]; else if (dst->fmt == B2R2_BLT_FMT_24_BIT_YUV888 || dst->fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || dst->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || dst->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) *vmx = &vmx_yvu_to_blt_yuv888[0]; else if (is_yuv_fmt(dst->fmt) && !is_yvu_fmt(dst->fmt)) *vmx = &vmx_yvu_to_yuv[0]; } else if (src->fmt == B2R2_BLT_FMT_24_BIT_YUV888 || src->fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || src->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || src->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) { /* * (A)YUV/VUY(A) formats differ only in component * order. This is handled by the endianness bit * in B2R2_STY/TTY registers when src/target are set. */ if (is_rgb_fmt(dst->fmt)) { *vmx = &vmx_blt_yuv888_to_rgb[0]; } else if (is_yvu_fmt(dst->fmt)) { *vmx = &vmx_blt_yuv888_to_yvu[0]; } else if (is_yuv_fmt(dst->fmt)) { switch (dst->fmt) { case B2R2_BLT_FMT_24_BIT_YUV888: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_24_BIT_VUY888: case B2R2_BLT_FMT_32_BIT_VUYA8888: /* do nothing */ break; default: *vmx = &vmx_blt_yuv888_to_yuv[0]; break; } } } else if (is_yuv_fmt(src->fmt)) { if (is_rgb_fmt(dst->fmt)) *vmx = &vmx_yuv_to_rgb[0]; else if (is_bgr_fmt(dst->fmt)) *vmx = &vmx_yuv_to_bgr[0]; else if (dst->fmt == B2R2_BLT_FMT_24_BIT_YUV888 || dst->fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || dst->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || dst->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) *vmx = &vmx_yuv_to_blt_yuv888[0]; else if (is_yvu_fmt(dst->fmt)) *vmx = &vmx_yvu_to_yuv[0]; } else if (is_bgr_fmt(src->fmt)) { if (is_rgb_fmt(dst->fmt)) *vmx = &vmx_rgb_to_bgr[0]; else if (is_yvu_fmt(dst->fmt)) *vmx = &vmx_bgr_to_yvu[0]; else if (dst->fmt == B2R2_BLT_FMT_24_BIT_YUV888 || dst->fmt == B2R2_BLT_FMT_32_BIT_AYUV8888 || dst->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || dst->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) BUG_ON(1); else if (is_yuv_fmt(dst->fmt)) *vmx = &vmx_bgr_to_yuv[0]; } if (dst->type == B2R2_FMT_TYPE_RASTER) { *node_count = 1; } else if (dst->type == B2R2_FMT_TYPE_SEMI_PLANAR) { *node_count = 2; } else if (dst->type == B2R2_FMT_TYPE_PLANAR) { *node_count = 3; } else { /* That's strange... */ BUG_ON(1); } return 0; } /** * analyze_color_fill() - analyze a color fill operation */ static int analyze_color_fill(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count) { int ret; /* Destination must be raster for raw fill to work */ if (this->dst.type != B2R2_FMT_TYPE_RASTER) { b2r2_log_warn("%s: fill requires raster destination\n", __func__); ret = -EINVAL; goto error; } /* We will try to fill the entire rectangle in one go */ memcpy(&this->dst.win, &this->dst.rect, sizeof(this->dst.win)); /* Check if this is a direct fill */ if ((!this->blend) && ((this->flags & B2R2_BLT_FLAG_SOURCE_FILL_RAW) || (this->dst.fmt == B2R2_BLT_FMT_32_BIT_ARGB8888) || (this->dst.fmt == B2R2_BLT_FMT_32_BIT_ABGR8888) || (this->dst.fmt == B2R2_BLT_FMT_32_BIT_AYUV8888) || (this->dst.fmt == B2R2_BLT_FMT_32_BIT_VUYA8888))) { this->type = B2R2_DIRECT_FILL; /* The color format will be the same as the dst fmt */ this->src.fmt = this->dst.fmt; /* The entire destination rectangle will be */ memcpy(&this->dst.win, &this->dst.rect, sizeof(this->dst.win)); *node_count = 1; } else { this->type = B2R2_FILL; /* Determine the fill color format */ if (this->flags & B2R2_BLT_FLAG_SOURCE_FILL_RAW) { /* The color format will be the same as the dst fmt */ this->src.fmt = this->dst.fmt; } else { /* If the dst fmt is YUV the fill fmt will be as well */ if (is_yuv_fmt(this->dst.fmt)) { this->src.fmt = B2R2_BLT_FMT_32_BIT_AYUV8888; } else if (is_rgb_fmt(this->dst.fmt)) { this->src.fmt = B2R2_BLT_FMT_32_BIT_ARGB8888; } else if (is_bgr_fmt(this->dst.fmt)) { /* Color will still be ARGB, we will translate using IVMX (configured later) */ this->src.fmt = B2R2_BLT_FMT_32_BIT_ARGB8888; } else { /* Wait, what? */ b2r2_log_warn("%s: " "Illegal destination format for fill", __func__); ret = -EINVAL; goto error; } } /* Also, B2R2 seems to ignore the pixel alpha value */ if (((this->flags & B2R2_BLT_FLAG_PER_PIXEL_ALPHA_BLEND) != 0) && ((this->flags & B2R2_BLT_FLAG_SOURCE_FILL_RAW) == 0) && fmt_has_alpha(this->src.fmt)) { u8 pixel_alpha = get_alpha(this->src.fmt, this->src.color); u32 new_global = pixel_alpha * this->global_alpha / 255; this->global_alpha = (u8)new_global; /* Set the pixel alpha to full opaque so we don't get any nasty surprises */ this->src.color = set_alpha(this->src.fmt, 0xFF, this->src.color); } ret = analyze_fmt_conv(&this->src, &this->dst, &this->ivmx, node_count); if (ret < 0) goto error; } return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * analyze_transform() - analyze a transform operation (rescale, rotate, etc.) */ static int analyze_transform(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count) { int ret; bool is_scaling; b2r2_log_info("%s\n", __func__); /* * The transform enum is defined so that all rotation transforms are * masked with the rotation flag */ this->rotation = (this->transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) != 0; /* B2R2 cannot do rotations if the destination is not raster, or 422R */ if (this->rotation && (this->dst.type != B2R2_FMT_TYPE_RASTER || this->dst.fmt == B2R2_BLT_FMT_Y_CB_Y_CR || this->dst.fmt == B2R2_BLT_FMT_CB_Y_CR_Y)) { b2r2_log_warn("%s: Unsupported operation " "(rot && (!dst_raster || dst==422R))", __func__); ret = -ENOSYS; goto unsupported; } /* Flip the image by changing the scan order of the destination */ if (this->transform & B2R2_BLT_TRANSFORM_FLIP_H) this->dst.hso = B2R2_TY_HSO_RIGHT_TO_LEFT; if (this->transform & B2R2_BLT_TRANSFORM_FLIP_V) this->dst.vso = B2R2_TY_VSO_BOTTOM_TO_TOP; /* Check for scaling */ if (this->rotation) { is_scaling = (this->src.rect.width != this->dst.rect.height) || (this->src.rect.height != this->dst.rect.width); } else { is_scaling = (this->src.rect.width != this->dst.rect.width) || (this->src.rect.height != this->dst.rect.height); } /* Plane separated formats must be treated as scaling */ is_scaling = is_scaling || (this->src.type == B2R2_FMT_TYPE_SEMI_PLANAR) || (this->src.type == B2R2_FMT_TYPE_PLANAR) || (this->dst.type == B2R2_FMT_TYPE_SEMI_PLANAR) || (this->dst.type == B2R2_FMT_TYPE_PLANAR); if (is_scaling && this->rotation && this->blend) { /* TODO: This is unsupported. Fix it! */ b2r2_log_info("%s: Unsupported operation (rot+rescale+blend)\n", __func__); ret = -ENOSYS; goto unsupported; } /* Check which type of transform */ if (is_scaling && this->rotation) { ret = analyze_rot_scale(this, req, node_count, buf_count); if (ret < 0) goto error; } else if (is_scaling) { ret = analyze_scaling(this, req, node_count, buf_count); if (ret < 0) goto error; } else if (this->rotation) { ret = analyze_rotate(this, req, node_count, buf_count); if (ret < 0) goto error; } else { /* No additional nodes needed for a flip */ ret = analyze_copy(this, req, node_count, buf_count); if (ret < 0) goto error; this->type = B2R2_FLIP; } return 0; error: b2r2_log_warn("%s: error!\n", __func__); unsupported: return ret; } /** * analyze_copy() - analyze a copy operation */ static int analyze_copy(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count) { int ret; memcpy(&this->dst.win, &this->dst.rect, sizeof(this->dst.win)); if (!this->blend && !(this->flags & B2R2_BLT_FLAG_CLUT_COLOR_CORRECTION) && (this->src.fmt == this->dst.fmt) && (this->src.type == B2R2_FMT_TYPE_RASTER) && (this->dst.rect.x >= this->clip_rect.x) && (this->dst.rect.y >= this->clip_rect.y) && (this->dst.rect.x + this->dst.rect.width <= this->clip_rect.x + this->clip_rect.width) && (this->dst.rect.y + this->dst.rect.height <= this->clip_rect.y + this->clip_rect.height)) { this->type = B2R2_DIRECT_COPY; *node_count = 1; } else { u32 copy_count; this->type = B2R2_COPY; ret = analyze_fmt_conv(&this->src, &this->dst, &this->ivmx, ©_count); if (ret < 0) goto error; *node_count = copy_count; } return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } static int calc_rot_count(u32 width, u32 height) { int count; count = width / B2R2_ROTATE_MAX_WIDTH; if (width % B2R2_ROTATE_MAX_WIDTH) count++; if (height > B2R2_ROTATE_MAX_WIDTH && height % B2R2_ROTATE_MAX_WIDTH) count *= 2; return count; } static int analyze_rot_scale_downscale(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count) { int ret; struct b2r2_node_split_buf *src = &this->src; struct b2r2_node_split_buf *dst = &this->dst; struct b2r2_node_split_buf *tmp = &this->tmp_bufs[0]; u32 num_rows; u32 num_cols; u32 rot_count; u32 rescale_count; u32 nodes_per_rot; u32 nodes_per_rescale; u32 right_width; u32 bottom_height; const u32 *dummy_vmx; b2r2_log_info("%s\n", __func__); /* Calculate the desired tmp buffer size */ tmp->win.width = rescale(B2R2_RESCALE_MAX_WIDTH - 1, this->h_rsf); tmp->win.width >>= 10; tmp->win.width = min(tmp->win.width, dst->rect.height); tmp->win.height = dst->rect.width; setup_tmp_buf(tmp, this->max_buf_size, dst->fmt, tmp->win.width, tmp->win.height); tmp->tmp_buf_index = 1; this->work_bufs[0].size = tmp->pitch * tmp->height; tmp->win.width = tmp->rect.width; tmp->win.height = tmp->rect.height; tmp->dither = dst->dither; dst->dither = 0; /* Update the dst window with the actual tmp buffer dimensions */ dst->win.width = tmp->win.height; dst->win.height = tmp->win.width; /* The rotated stripes are written to the destination bottom-up */ if (this->dst.vso == B2R2_TY_VSO_TOP_TO_BOTTOM) this->dst.vso = B2R2_TY_VSO_BOTTOM_TO_TOP; else this->dst.vso = B2R2_TY_VSO_TOP_TO_BOTTOM; /* * Calculate how many nodes are required to copy to and from the tmp * buffer */ ret = analyze_fmt_conv(src, tmp, &this->ivmx, &nodes_per_rescale); if (ret < 0) goto error; /* We will not do any format conversion in the rotation stage */ ret = analyze_fmt_conv(tmp, dst, &dummy_vmx, &nodes_per_rot); if (ret < 0) goto error; /* Calculate node count for the inner tiles */ num_cols = dst->rect.width / dst->win.width; num_rows = dst->rect.height / dst->win.height; rescale_count = num_cols * num_rows; rot_count = calc_rot_count(dst->win.height, dst->win.width) * num_cols * num_rows; right_width = dst->rect.width % dst->win.width; bottom_height = dst->rect.height % dst->win.height; /* Calculate node count for the rightmost tiles */ if (right_width) { u32 count = calc_rot_count(dst->win.height, right_width); rot_count += count * num_rows; rescale_count += num_rows; b2r2_log_info("%s: rightmost: %d nodes\n", __func__, count*num_rows); } /* Calculate node count for the bottom tiles */ if (bottom_height) { u32 count = calc_rot_count(bottom_height, dst->win.width); rot_count += count * num_cols; rescale_count += num_cols; b2r2_log_info("%s: bottom: %d nodes\n", __func__, count * num_cols); } /* And finally for the bottom right corner */ if (right_width && bottom_height) { u32 count = calc_rot_count(bottom_height, right_width); rot_count += count; rescale_count++; b2r2_log_info("%s: bottom right: %d nodes\n", __func__, count); } *node_count = rot_count * nodes_per_rot; *node_count += rescale_count * nodes_per_rescale; *buf_count = 1; return 0; error: b2r2_log_warn("%s: error!\n", __func__); return ret; } static int analyze_rot_scale_upscale(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count) { /* TODO: When upscaling we should optimally to the rotation first... */ return analyze_rot_scale_downscale(this, req, node_count, buf_count); } /** * analyze_rot_scaling() - analyzes a combined rotation and scaling op */ static int analyze_rot_scale(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count) { int ret; bool upscale; ret = analyze_scale_factors(this); if (ret < 0) goto error; upscale = (u32)this->h_rsf * (u32)this->v_rsf < (1 << 20); if (upscale) ret = analyze_rot_scale_upscale(this, req, node_count, buf_count); else ret = analyze_rot_scale_downscale(this, req, node_count, buf_count); if (ret < 0) goto error; this->type = B2R2_SCALE_AND_ROTATE; return 0; error: return ret; } /** * analyze_scaling() - analyze a rescale operation */ static int analyze_scaling(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count) { int ret; u32 copy_count; u32 nbr_cols; s32 dst_w; b2r2_log_info("%s\n", __func__); ret = analyze_scale_factors(this); if (ret < 0) goto error; /* Find out how many nodes a simple copy would require */ ret = analyze_fmt_conv(&this->src, &this->dst, &this->ivmx, ©_count); if (ret < 0) goto error; memcpy(&this->dst.win, &this->dst.rect, sizeof(this->dst.win)); /* * We need to subtract from the actual maximum rescale width since the * start of the stripe will be floored and the end ceiled. This could in * some cases cause the stripe to be one pixel more than the maximum * width. * * Example: * x = 127.8, w = 127.8 * * The stripe will touch pixels 127.8 through 255.6, i.e. 129 pixels. */ dst_w = rescale(B2R2_RESCALE_MAX_WIDTH - 1, this->h_rsf); if (dst_w < (1 << 10)) dst_w = 1; else dst_w >>= 10; b2r2_log_info("%s: dst_w=%d dst.rect.width=%d\n", __func__, dst_w, this->dst.rect.width); this->dst.win.width = min(dst_w, this->dst.rect.width); b2r2_log_info("%s: dst.win.width=%d\n", __func__, this->dst.win.width); nbr_cols = this->dst.rect.width / this->dst.win.width; if (this->dst.rect.width % this->dst.win.width) nbr_cols++; *node_count = copy_count * nbr_cols; this->type = B2R2_SCALE; b2r2_log_info("%s exit\n", __func__); return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * analyze_rotate() - analyze a rotate operation */ static int analyze_rotate(struct b2r2_node_split_job *this, const struct b2r2_blt_request *req, u32 *node_count, u32 *buf_count) { int ret; u32 nodes_per_tile; /* Find out how many nodes a simple copy would require */ ret = analyze_fmt_conv(&this->src, &this->dst, &this->ivmx, &nodes_per_tile); if (ret < 0) goto error; this->type = B2R2_ROTATE; /* The rotated stripes are written to the destination bottom-up */ if (this->dst.vso == B2R2_TY_VSO_TOP_TO_BOTTOM) this->dst.vso = B2R2_TY_VSO_BOTTOM_TO_TOP; else this->dst.vso = B2R2_TY_VSO_TOP_TO_BOTTOM; memcpy(&this->dst.win, &this->dst.rect, sizeof(this->dst.win)); this->dst.win.height = min(this->dst.win.height, B2R2_ROTATE_MAX_WIDTH); /* * B2R2 cannot do rotations on stripes that are not a multiple of 16 * pixels high (if larger than 16 pixels). */ if (this->dst.win.width > 16) this->dst.win.width -= (this->dst.win.width % 16); /* Blending cannot be combined with rotation */ if (this->blend) { struct b2r2_node_split_buf *tmp = &this->tmp_bufs[0]; enum b2r2_blt_fmt tmp_fmt; if (is_yuv_fmt(this->dst.fmt)) tmp_fmt = B2R2_BLT_FMT_32_BIT_AYUV8888; else if (is_bgr_fmt(this->dst.fmt)) tmp_fmt = B2R2_BLT_FMT_32_BIT_ABGR8888; else tmp_fmt = B2R2_BLT_FMT_32_BIT_ARGB8888; setup_tmp_buf(tmp, this->max_buf_size, tmp_fmt, this->dst.win.width, this->dst.win.height); tmp->tmp_buf_index = 1; tmp->vso = B2R2_TY_VSO_BOTTOM_TO_TOP; this->dst.win.width = tmp->rect.width; this->dst.win.height = tmp->rect.height; memcpy(&tmp->win, &tmp->rect, sizeof(tmp->win)); *buf_count = 1; this->work_bufs[0].size = tmp->pitch * tmp->height; /* * One more node per tile is required to rotate to the temp * buffer. */ nodes_per_tile++; } /* Finally, calculate the node count */ *node_count = nodes_per_tile * calc_rot_count(this->src.rect.width, this->src.rect.height); return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * analyze_scale_factors() - determines the scale factors for the op */ static int analyze_scale_factors(struct b2r2_node_split_job *this) { int ret; u16 hsf; u16 vsf; if (this->rotation) { ret = calculate_scale_factor(this->src.rect.width, this->dst.rect.height, &hsf); if (ret < 0) goto error; ret = calculate_scale_factor(this->src.rect.height, this->dst.rect.width, &vsf); if (ret < 0) goto error; } else { ret = calculate_scale_factor(this->src.rect.width, this->dst.rect.width, &hsf); if (ret < 0) goto error; ret = calculate_scale_factor(this->src.rect.height, this->dst.rect.height, &vsf); if (ret < 0) goto error; } this->h_rescale = hsf != (1 << 10); this->v_rescale = vsf != (1 << 10); this->h_rsf = hsf; this->v_rsf = vsf; b2r2_log_info("%s: h_rsf=%.4x\n", __func__, this->h_rsf); b2r2_log_info("%s: v_rsf=%.4x\n", __func__, this->v_rsf); return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * configure_tile() - configures one tile of a blit operation */ static int configure_tile(struct b2r2_node_split_job *this, struct b2r2_node *node, struct b2r2_node **next) { int ret = 0; struct b2r2_node *last; struct b2r2_node_split_buf *src = &this->src; struct b2r2_node_split_buf *dst = &this->dst; struct b2r2_node_split_buf *bg = &this->bg; struct b2r2_blt_rect dst_norm; struct b2r2_blt_rect src_norm; struct b2r2_blt_rect bg_norm; /* Normalize the dest coords to the dest rect coordinate space */ dst_norm.x = dst->win.x - dst->rect.x; dst_norm.y = dst->win.y - dst->rect.y; dst_norm.width = dst->win.width; dst_norm.height = dst->win.height; if (dst->vso == B2R2_TY_VSO_BOTTOM_TO_TOP) { /* The y coord should be counted from the bottom */ dst_norm.y = dst->rect.height - (dst_norm.y + 1); } if (dst->hso == B2R2_TY_HSO_RIGHT_TO_LEFT) { /* The x coord should be counted from the right */ dst_norm.x = dst->rect.width - (dst_norm.x + 1); } /* If the destination is rotated we should swap x, y */ if (this->rotation) { src_norm.x = dst_norm.y; src_norm.y = dst_norm.x; src_norm.width = dst_norm.height; src_norm.height = dst_norm.width; } else { src_norm.x = dst_norm.x; src_norm.y = dst_norm.y; src_norm.width = dst_norm.width; src_norm.height = dst_norm.height; } /* Convert to src coordinate space */ src->win.x = src_norm.x + src->rect.x; src->win.y = src_norm.y + src->rect.y; src->win.width = src_norm.width; src->win.height = src_norm.height; /* Set bg norm */ bg_norm.x = dst->win.x - dst->rect.x; bg_norm.y = dst->win.y - dst->rect.y; bg_norm.width = dst->win.width; bg_norm.height = dst->win.height; /* Convert to bg coordinate space */ bg->win.x = bg_norm.x + bg->rect.x; bg->win.y = bg_norm.y + bg->rect.y; bg->win.width = bg_norm.width; bg->win.height = bg_norm.height; bg->vso = dst->vso; bg->hso = dst->hso; /* Do the configuration depending on operation type */ switch (this->type) { case B2R2_DIRECT_FILL: configure_direct_fill(node, this->src.color, dst, &last); break; case B2R2_DIRECT_COPY: configure_direct_copy(node, src, dst, &last); break; case B2R2_FILL: ret = configure_fill(node, src->color, src->fmt, dst, this->ivmx, &last); break; case B2R2_FLIP: /* FLIP is just a copy with different VSO/HSO */ case B2R2_COPY: ret = configure_copy(node, src, dst, this->ivmx, &last, this); break; case B2R2_ROTATE: { struct b2r2_node_split_buf *tmp = &this->tmp_bufs[0]; if (this->blend) { b2r2_log_info("%s: rotation + blend\n", __func__); tmp->win.x = 0; tmp->win.y = tmp->win.height - 1; tmp->win.width = dst->win.width; tmp->win.height = dst->win.height; /* Rotate to the temp buf */ ret = configure_rotate(node, src, tmp, this->ivmx, &node, NULL); if (ret < 0) goto error; /* Then do a copy to the destination */ ret = configure_copy(node, tmp, dst, NULL, &last, this); } else { /* Just do a rotation */ ret = configure_rotate(node, src, dst, this->ivmx, &last, this); } } break; case B2R2_SCALE: ret = configure_scale(node, src, dst, this->h_rsf, this->v_rsf, this->ivmx, &last, this); break; case B2R2_SCALE_AND_ROTATE: ret = configure_rot_scale(this, node, &last); break; default: b2r2_log_warn("%s: Unsupported request\n", __func__); ret = -ENOSYS; goto error; break; } if (ret < 0) goto error; /* Scale and rotate will configure its own blending and clipping */ if (this->type != B2R2_SCALE_AND_ROTATE) { /* Configure blending and clipping */ do { if (node == NULL) { b2r2_log_warn("%s: " "Internal error! Out of nodes!\n", __func__); ret = -ENOMEM; goto error; } if (this->blend) { if (this->flags & B2R2_BLT_FLAG_BG_BLEND) { configure_bg(node, bg, this->swap_fg_bg); } else { configure_bg(node, dst, this->swap_fg_bg); } configure_blend(node, this->flags, this->global_alpha); } if (this->clip) configure_clip(node, &this->clip_rect); node = node->next; } while (node != last); } /* Consume the nodes */ *next = last; return 0; error: b2r2_log_warn("%s: Error!\n", __func__); return ret; } /* * configure_sub_rot() - configure a sub-rotation * * This functions configures a set of nodes for rotation using the destination * window instead of the rectangle for calculating tiles. */ static int configure_sub_rot(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next, struct b2r2_node_split_job *job) { int ret; struct b2r2_blt_rect src_win; struct b2r2_blt_rect dst_win; u32 y_pixels = 0; u32 x_pixels = 0; memcpy(&src_win, &src->win, sizeof(src_win)); memcpy(&dst_win, &dst->win, sizeof(dst_win)); b2r2_log_info("%s: src_win=(%d, %d, %d, %d) " "dst_win=(%d, %d, %d, %d)\n", __func__, src_win.x, src_win.y, src_win.width, src_win.height, dst_win.x, dst_win.y, dst_win.width, dst_win.height); dst->win.height = B2R2_ROTATE_MAX_WIDTH; if (dst->win.width % B2R2_ROTATE_MAX_WIDTH) dst->win.width -= dst->win.width % B2R2_ROTATE_MAX_WIDTH; while (x_pixels < dst_win.width) { u32 src_x = src->win.x; u32 src_w = src->win.width; u32 dst_y = dst->win.y; u32 dst_h = dst->win.height; dst->win.width = min(dst->win.width, dst_win.width - (int)x_pixels); src->win.height = dst->win.width; b2r2_log_info("%s: x_pixels=%d\n", __func__, x_pixels); while (y_pixels < dst_win.height) { dst->win.height = min(dst->win.height, dst_win.height - (int)y_pixels); src->win.width = dst->win.height; b2r2_log_info("%s: y_pixels=%d\n", __func__, y_pixels); ret = configure_rotate(node, src, dst, ivmx, &node, job); if (ret < 0) goto error; src->win.x += (src->hso == B2R2_TY_HSO_LEFT_TO_RIGHT) ? src->win.width : -src->win.width; dst->win.y += (dst->vso == B2R2_TY_VSO_TOP_TO_BOTTOM) ? dst->win.height : -dst->win.height; y_pixels += dst->win.height; } src->win.x = src_x; src->win.y += (src->vso == B2R2_TY_VSO_TOP_TO_BOTTOM) ? src->win.height : -src->win.height; src->win.width = src_w; dst->win.x += (dst->hso == B2R2_TY_HSO_LEFT_TO_RIGHT) ? dst->win.width : -dst->win.width; dst->win.y = dst_y; dst->win.height = dst_h; x_pixels += dst->win.width; y_pixels = 0; } memcpy(&src->win, &src_win, sizeof(src->win)); memcpy(&dst->win, &dst_win, sizeof(dst->win)); *next = node; return 0; error: b2r2_log_warn("%s: error!\n", __func__); return ret; } /** * configure_rot_downscale() - configures a combined rotate and downscale * * When doing a downscale it is better to do the rotation last. */ static int configure_rot_downscale(struct b2r2_node_split_job *this, struct b2r2_node *node, struct b2r2_node **next) { int ret; struct b2r2_node_split_buf *src = &this->src; struct b2r2_node_split_buf *dst = &this->dst; struct b2r2_node_split_buf *tmp = &this->tmp_bufs[0]; tmp->win.x = 0; tmp->win.y = 0; tmp->win.width = dst->win.height; tmp->win.height = dst->win.width; ret = configure_scale(node, src, tmp, this->h_rsf, this->v_rsf, this->ivmx, &node, this); if (ret < 0) goto error; ret = configure_sub_rot(node, tmp, dst, NULL, &node, this); if (ret < 0) goto error; *next = node; return 0; error: b2r2_log_info("%s: error!\n", __func__); return ret; } /** * configure_rot_upscale() - configures a combined rotate and upscale * * When doing an upscale it is better to do the rotation first. */ static int configure_rot_upscale(struct b2r2_node_split_job *this, struct b2r2_node *node, struct b2r2_node **next) { /* TODO: Implement a optimal upscale (rotation first) */ return configure_rot_downscale(this, node, next); } /** * configure_rot_scale() - configures a combined rotation and scaling op */ static int configure_rot_scale(struct b2r2_node_split_job *this, struct b2r2_node *node, struct b2r2_node **next) { int ret; bool upscale = (u32)this->h_rsf * (u32)this->v_rsf < (1 << 10); if (upscale) ret = configure_rot_upscale(this, node, next); else ret = configure_rot_downscale(this, node, next); if (ret < 0) goto error; return 0; error: b2r2_log_warn("%s: error!\n", __func__); return ret; } /** * configure_direct_fill() - configures the given node for direct fill * * @node - the node to configure * @color - the fill color * @dst - the destination buffer * @next - the next empty node in the node list * * This operation will always consume one node only. */ static void configure_direct_fill(struct b2r2_node *node, u32 color, struct b2r2_node_split_buf *dst, struct b2r2_node **next) { node->node.GROUP0.B2R2_CIC |= B2R2_CIC_COLOR_FILL | B2R2_CIC_SOURCE_1; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_1_DIRECT_FILL; /* Target setup */ set_target(node, dst->addr, dst); /* Source setup */ /* It seems B2R2 checks so that source and dest has the same format */ node->node.GROUP3.B2R2_STY = to_native_fmt(dst->fmt); node->node.GROUP2.B2R2_S1CF = color; node->node.GROUP2.B2R2_S2CF = 0; /* Consume the node */ *next = node->next; } /** * configure_direct_copy() - configures the node for direct copy * * @node - the node to configure * @src - the source buffer * @dst - the destination buffer * @next - the next empty node in the node list * * This operation will always consume one node only. */ static void configure_direct_copy(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, struct b2r2_node **next) { node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_1; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_1_DIRECT_COPY; /* Source setup, use the base function to avoid altering the INS */ set_src(&node->node.GROUP3, src->addr, src); /* Target setup */ set_target(node, dst->addr, dst); /* Consume the node */ *next = node->next; } /** * configure_fill() - configures the given node for color fill * * @node - the node to configure * @color - the fill color * @fmt - the source color format * @dst - the destination buffer * @next - the next empty node in the node list * * A normal fill operation can be combined with any other per pixel operations * such as blend. * * This operation will consume as many nodes as are required to write to the * destination format. */ static int configure_fill(struct b2r2_node *node, u32 color, enum b2r2_blt_fmt fmt, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next) { int ret; struct b2r2_node *last; /* Configure the destination */ ret = configure_dst(node, dst, ivmx, &last); if (ret < 0) goto error; do { if (node == NULL) { b2r2_log_warn("%s: " "Internal error! Out of nodes!\n", __func__); ret = -ENOMEM; goto error; } node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_2 | B2R2_CIC_COLOR_FILL; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_2_COLOR_FILL_REGISTER; node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BYPASS_S2_S3; /* B2R2 has a bug that disables color fill from S2. As a workaround we use S1 for the color. */ node->node.GROUP2.B2R2_S1CF = 0; node->node.GROUP2.B2R2_S2CF = color; /* TO BE REMOVED: */ set_src_2(node, dst->addr, dst); node->node.GROUP4.B2R2_STY = to_native_fmt(fmt); /* Setup the iVMX for color conversion */ if (ivmx != NULL) set_ivmx(node, ivmx); if ((dst->type == B2R2_FMT_TYPE_PLANAR) || (dst->type == B2R2_FMT_TYPE_SEMI_PLANAR)) { node->node.GROUP0.B2R2_INS |= B2R2_INS_RESCALE2D_ENABLED; node->node.GROUP8.B2R2_FCTL = B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER | B2R2_FCTL_LUMA_HF2D_MODE_ENABLE_RESIZER | B2R2_FCTL_LUMA_VF2D_MODE_ENABLE_RESIZER; node->node.GROUP9.B2R2_RSF = (1 << (B2R2_RSF_HSRC_INC_SHIFT + 10)) | (1 << (B2R2_RSF_VSRC_INC_SHIFT + 10)); node->node.GROUP9.B2R2_RZI = B2R2_RZI_DEFAULT_HNB_REPEAT | (2 << B2R2_RZI_VNB_REPEAT_SHIFT); node->node.GROUP10.B2R2_RSF = (1 << (B2R2_RSF_HSRC_INC_SHIFT + 10)) | (1 << (B2R2_RSF_VSRC_INC_SHIFT + 10)); node->node.GROUP10.B2R2_RZI = B2R2_RZI_DEFAULT_HNB_REPEAT | (2 << B2R2_RZI_VNB_REPEAT_SHIFT); } node = node->next; } while (node != last); /* Consume the nodes */ *next = node; return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * configure_copy() - configures the given node for a copy operation * * @node - the node to configure * @src - the source buffer * @dst - the destination buffer * @ivmx - the iVMX to use for color conversion * @next - the next empty node in the node list * * This operation will consume as many nodes as are required to write to the * destination format. */ static int configure_copy(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next, struct b2r2_node_split_job *this) { int ret; struct b2r2_node *last; ret = configure_dst(node, dst, ivmx, &last); if (ret < 0) goto error; /* Configure the source for each node */ do { if (node == NULL) { b2r2_log_warn("%s: " " Internal error! Out of nodes!\n", __func__); ret = -ENOMEM; goto error; } node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BYPASS_S2_S3; if (this != NULL && (this->flags & B2R2_BLT_FLAG_SOURCE_COLOR_KEY) != 0) { u32 key_color = 0; node->node.GROUP0.B2R2_ACK |= B2R2_ACK_CKEY_SEL_SRC_AFTER_CLUT | B2R2_ACK_CKEY_RED_MATCH_IF_BETWEEN | B2R2_ACK_CKEY_GREEN_MATCH_IF_BETWEEN | B2R2_ACK_CKEY_BLUE_MATCH_IF_BETWEEN; node->node.GROUP0.B2R2_INS |= B2R2_INS_CKEY_ENABLED; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_COLOR_KEY; key_color = to_RGB888(this->flag_param, src->fmt); node->node.GROUP12.B2R2_KEY1 = key_color; node->node.GROUP12.B2R2_KEY2 = key_color; } if (this != NULL && (this->flags & B2R2_BLT_FLAG_CLUT_COLOR_CORRECTION) != 0) { struct b2r2_blt_request *request = container_of(this, struct b2r2_blt_request, node_split_job); node->node.GROUP0.B2R2_INS |= B2R2_INS_CLUTOP_ENABLED; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_CLUT; node->node.GROUP7.B2R2_CCO = B2R2_CCO_CLUT_COLOR_CORRECTION | B2R2_CCO_CLUT_UPDATE; node->node.GROUP7.B2R2_CML = request->clut_phys_addr; } /* Configure the source(s) */ configure_src(node, src, ivmx); node = node->next; } while (node != last); /* Consume the nodes */ *next = node; return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * configure_rotate() - configures the given node for rotation * * @node - the node to configure * @src - the source buffer * @dst - the destination buffer * @ivmx - the iVMX to use for color conversion * @next - the next empty node in the node list * * This operation will consume as many nodes are are required by the combination * of rotating and writing the destination format. */ static int configure_rotate(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next, struct b2r2_node_split_job *this) { int ret; struct b2r2_node *last; ret = configure_copy(node, src, dst, ivmx, &last, this); if (ret < 0) goto error; do { if (node == NULL) { b2r2_log_warn("%s: " "Internal error! Out of nodes!\n", __func__); ret = -ENOMEM; goto error; } node->node.GROUP0.B2R2_INS |= B2R2_INS_ROTATION_ENABLED; b2r2_log_debug("%s:\n" "\tB2R2_TXY: %.8x\tB2R2_TSZ: %.8x\n" "\tB2R2_S1XY: %.8x\tB2R2_S1SZ: %.8x\n" "\tB2R2_S2XY: %.8x\tB2R2_S2SZ: %.8x\n" "\tB2R2_S3XY: %.8x\tB2R2_S3SZ: %.8x\n" "-----------------------------------\n", __func__, node->node.GROUP1.B2R2_TXY, node->node.GROUP1.B2R2_TSZ, node->node.GROUP3.B2R2_SXY, node->node.GROUP3.B2R2_SSZ, node->node.GROUP4.B2R2_SXY, node->node.GROUP4.B2R2_SSZ, node->node.GROUP5.B2R2_SXY, node->node.GROUP5.B2R2_SSZ); node = node->next; } while (node != last); /* Consume the nodes */ *next = node; return 0; error: b2r2_log_warn("%s: error!\n", __func__); return ret; } /** * configure_scale() - configures the given node for scaling * * @node - the node to configure * @src - the source buffer * @dst - the destination buffer * @h_rsf - the horizontal rescale factor * @v_rsf - the vertical rescale factor * @ivmx - the iVMX to use for color conversion * @next - the next empty node in the node list */ static int configure_scale(struct b2r2_node *node, struct b2r2_node_split_buf *src, struct b2r2_node_split_buf *dst, u16 h_rsf, u16 v_rsf, const u32 *ivmx, struct b2r2_node **next, struct b2r2_node_split_job *this) { int ret; struct b2r2_node *last; struct b2r2_filter_spec *hf = NULL; struct b2r2_filter_spec *vf = NULL; u32 fctl = 0; u32 rsf = 0; u32 rzi = 0; u32 hsrc_init = 0; u32 vsrc_init = 0; u32 hfp = 0; u32 vfp = 0; u16 luma_h_rsf = h_rsf; u16 luma_v_rsf = v_rsf; struct b2r2_filter_spec *luma_hf = NULL; struct b2r2_filter_spec *luma_vf = NULL; u32 luma_fctl = 0; u32 luma_rsf = 0; u32 luma_rzi = 0; u32 luma_hsrc_init = 0; u32 luma_vsrc_init = 0; u32 luma_hfp = 0; u32 luma_vfp = 0; s32 src_x; s32 src_y; s32 src_w; s32 src_h; bool upsample; bool downsample; struct b2r2_blt_rect tmp_win = src->win; /* Rescale the normalized source window */ src_x = inv_rescale(src->win.x - src->rect.x, luma_h_rsf); src_y = inv_rescale(src->win.y - src->rect.y, luma_v_rsf); src_w = inv_rescale(src->win.width, luma_h_rsf); src_h = inv_rescale(src->win.height, luma_v_rsf); /* Convert to src coordinate space */ src->win.x = (src_x >> 10) + src->rect.x; src->win.y = (src_y >> 10) + src->rect.y; /* * Since the stripe might start and end on a fractional pixel * we need to count all the touched pixels in the width. * * Example: * src_x = 1.8, src_w = 2.8 * * The stripe touches pixels 1.8 through 4.6, i.e. 4 pixels */ src->win.width = ((src_x & 0x3ff) + src_w + 0x3ff) >> 10; src->win.height = ((src_y & 0x3ff) + src_h + 0x3ff) >> 10; luma_hsrc_init = src_x & 0x3ff; luma_vsrc_init = src_y & 0x3ff; /* Check for upsampling of chroma */ upsample = src->type != B2R2_FMT_TYPE_RASTER && !is_yuv444_fmt(src->fmt); if (upsample) { h_rsf /= 2; if (is_yuv420_fmt(src->fmt)) v_rsf /= 2; } /* Check for downsampling of chroma */ downsample = dst->type != B2R2_FMT_TYPE_RASTER && !is_yuv444_fmt(dst->fmt); if (downsample) { h_rsf *= 2; if (is_yuv420_fmt(dst->fmt)) v_rsf *= 2; } src_x = inv_rescale(tmp_win.x - src->rect.x, h_rsf); src_y = inv_rescale(tmp_win.y - src->rect.y, v_rsf); hsrc_init = src_x & 0x3ff; vsrc_init = src_y & 0x3ff; /* Configure resize and filters */ fctl = B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; luma_fctl = B2R2_FCTL_LUMA_HF2D_MODE_ENABLE_RESIZER | B2R2_FCTL_LUMA_VF2D_MODE_ENABLE_RESIZER; rsf = (h_rsf << B2R2_RSF_HSRC_INC_SHIFT) | (v_rsf << B2R2_RSF_VSRC_INC_SHIFT); luma_rsf = (luma_h_rsf << B2R2_RSF_HSRC_INC_SHIFT) | (luma_v_rsf << B2R2_RSF_VSRC_INC_SHIFT); rzi = B2R2_RZI_DEFAULT_HNB_REPEAT | (2 << B2R2_RZI_VNB_REPEAT_SHIFT) | (hsrc_init << B2R2_RZI_HSRC_INIT_SHIFT) | (vsrc_init << B2R2_RZI_VSRC_INIT_SHIFT); luma_rzi = B2R2_RZI_DEFAULT_HNB_REPEAT | (2 << B2R2_RZI_VNB_REPEAT_SHIFT) | (luma_hsrc_init << B2R2_RZI_HSRC_INIT_SHIFT) | (luma_vsrc_init << B2R2_RZI_VSRC_INIT_SHIFT); /* * We should only filter if there is an actual rescale (i.e. not when * up or downsampling). */ if (luma_h_rsf != (1 << 10)) { hf = b2r2_filter_find(h_rsf); luma_hf = b2r2_filter_find(luma_h_rsf); } if (luma_v_rsf != (1 << 10)) { vf = b2r2_filter_find(v_rsf); luma_vf = b2r2_filter_find(luma_v_rsf); } if (hf) { fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_COLOR_CHANNEL_FILTER; hfp = hf->h_coeffs_phys_addr; } if (vf) { fctl |= B2R2_FCTL_VF2D_MODE_ENABLE_COLOR_CHANNEL_FILTER; vfp = vf->v_coeffs_phys_addr; } if (luma_hf) { luma_fctl |= B2R2_FCTL_LUMA_HF2D_MODE_ENABLE_FILTER; luma_hfp = luma_hf->h_coeffs_phys_addr; } if (luma_vf) { luma_fctl |= B2R2_FCTL_LUMA_VF2D_MODE_ENABLE_FILTER; luma_vfp = luma_vf->v_coeffs_phys_addr; } ret = configure_copy(node, src, dst, ivmx, &last, this); if (ret < 0) goto error; do { bool chroma_rescale = (h_rsf != (1 << 10)) || (v_rsf != (1 << 10)); bool luma_rescale = (luma_h_rsf != (1 << 10)) || (luma_v_rsf != (1 << 10)); bool src_raster = src->type == B2R2_FMT_TYPE_RASTER; bool dst_raster = dst->type == B2R2_FMT_TYPE_RASTER; bool dst_chroma = node->node.GROUP1.B2R2_TTY & B2R2_TTY_CHROMA_NOT_LUMA; if (node == NULL) { b2r2_log_warn("%s: " "Internal error! Out of nodes!\n", __func__); ret = -ENOMEM; goto error; } node->node.GROUP0.B2R2_CIC |= B2R2_CIC_FILTER_CONTROL; /* * If the source format is anything other than raster, we * always have to enable both chroma and luma resizers. This * could be a bug in the hardware, since it is not mentioned in * the specification. * * Otherwise, we will only enable the chroma resizer when * writing chroma and the luma resizer when writing luma * (or both when writing raster). Also, if there is no rescale * to be done there's no point in using the resizers. */ if (!src_raster || (chroma_rescale && (dst_raster || dst_chroma))) { /* Enable chroma resize */ node->node.GROUP0.B2R2_INS |= B2R2_INS_RESCALE2D_ENABLED; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_RESIZE_CHROMA; node->node.GROUP8.B2R2_FCTL |= fctl; node->node.GROUP9.B2R2_RSF = rsf; node->node.GROUP9.B2R2_RZI = rzi; node->node.GROUP9.B2R2_HFP = hfp; node->node.GROUP9.B2R2_VFP = vfp; } if (!src_raster || (luma_rescale && (dst_raster || !dst_chroma))) { /* Enable luma resize */ node->node.GROUP0.B2R2_INS |= B2R2_INS_RESCALE2D_ENABLED; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_RESIZE_LUMA; node->node.GROUP8.B2R2_FCTL |= luma_fctl; node->node.GROUP10.B2R2_RSF = luma_rsf; node->node.GROUP10.B2R2_RZI = luma_rzi; node->node.GROUP10.B2R2_HFP = luma_hfp; node->node.GROUP10.B2R2_VFP = luma_vfp; /* * Scaling operation from raster to a multi-buffer * format, requires the raster input to be scaled * before luminance information can be extracted. * Raster input is scaled by the chroma resizer. * Luma resizer only handles luminance data which * exists in a separate buffer in source image, * as is the case with YUV planar/semi-planar formats. */ if (src_raster) { /* Activate chroma scaling */ node->node.GROUP0.B2R2_CIC |= B2R2_CIC_RESIZE_CHROMA; node->node.GROUP8.B2R2_FCTL |= fctl; /* * Color data must be scaled * to the same size as luma. * Use luma scaling parameters. */ node->node.GROUP9.B2R2_RSF = luma_rsf; node->node.GROUP9.B2R2_RZI = luma_rzi; node->node.GROUP9.B2R2_HFP = luma_hfp; node->node.GROUP9.B2R2_VFP = luma_vfp; } } b2r2_log_info("%s:\n" "\tB2R2_TXY: %.8x\tB2R2_TSZ: %.8x\n" "\tB2R2_S1XY: %.8x\tB2R2_S1SZ: %.8x\n" "\tB2R2_S2XY: %.8x\tB2R2_S2SZ: %.8x\n" "\tB2R2_S3XY: %.8x\tB2R2_S3SZ: %.8x\n" "----------------------------------\n", __func__, node->node.GROUP1.B2R2_TXY, node->node.GROUP1.B2R2_TSZ, node->node.GROUP3.B2R2_SXY, node->node.GROUP3.B2R2_SSZ, node->node.GROUP4.B2R2_SXY, node->node.GROUP4.B2R2_SSZ, node->node.GROUP5.B2R2_SXY, node->node.GROUP5.B2R2_SSZ); node = node->next; } while (node != last); /* Consume the nodes */ *next = node; return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * configure_src() - configures the source registers and the iVMX * * @node - the node to configure * @src - the source buffer * @ivmx - the iVMX to use for color conversion * * This operation will not consume any nodes */ static void configure_src(struct b2r2_node *node, struct b2r2_node_split_buf *src, const u32 *ivmx) { struct b2r2_node_split_buf tmp_buf; b2r2_log_info("%s: src.win=(%d, %d, %d, %d)\n", __func__, src->win.x, src->win.y, src->win.width, src->win.height); /* Configure S1 - S3 */ switch (src->type) { case B2R2_FMT_TYPE_RASTER: set_src_2(node, src->addr, src); break; case B2R2_FMT_TYPE_SEMI_PLANAR: memcpy(&tmp_buf, src, sizeof(tmp_buf)); /* * For 420 and 422 the chroma has lower resolution than the * luma */ if (!is_yuv444_fmt(src->fmt)) { tmp_buf.win.x >>= 1; tmp_buf.win.width = (tmp_buf.win.width + 1) / 2; if (is_yuv420_fmt(src->fmt)) { tmp_buf.win.height = (tmp_buf.win.height + 1) / 2; tmp_buf.win.y >>= 1; } } set_src_3(node, src->addr, src); set_src_2(node, tmp_buf.chroma_addr, &tmp_buf); break; case B2R2_FMT_TYPE_PLANAR: memcpy(&tmp_buf, src, sizeof(tmp_buf)); if (!is_yuv444_fmt(src->fmt)) { /* * Each chroma buffer will have half as many values * per line as the luma buffer */ tmp_buf.pitch = (tmp_buf.pitch + 1) / 2; /* Horizontal resolution is half */ tmp_buf.win.x >>= 1; tmp_buf.win.width = (tmp_buf.win.width + 1) / 2; /* * If the buffer is in YUV420 format, the vertical * resolution is half as well */ if (is_yuv420_fmt(src->fmt)) { tmp_buf.win.height = (tmp_buf.win.height + 1) / 2; tmp_buf.win.y >>= 1; } } set_src_3(node, src->addr, src); /* Y */ set_src_2(node, tmp_buf.chroma_addr, &tmp_buf); /* U */ set_src_1(node, tmp_buf.chroma_cr_addr, &tmp_buf); /* V */ break; default: /* Should never, ever happen */ BUG_ON(1); break; } /* Configure the iVMX for color space conversions */ if (ivmx != NULL) set_ivmx(node, ivmx); } /** * configure_bg() - configures a background for the given node * * @node - the node to configure * @bg - the background buffer * @swap_fg_bg - if true, fg will be on s1 instead of s2 * * This operation will not consume any nodes. * * NOTE: This method should be called _AFTER_ the destination has been * configured. * * WARNING: Take care when using this with semi-planar or planar sources since * either S1 or S2 will be overwritten! */ static void configure_bg(struct b2r2_node *node, struct b2r2_node_split_buf *bg, bool swap_fg_bg) { b2r2_log_info("%s: bg.win=(%d, %d, %d, %d)\n", __func__, bg->win.x, bg->win.y, bg->win.width, bg->win.height); /* Configure S1 */ switch (bg->type) { case B2R2_FMT_TYPE_RASTER: if (swap_fg_bg) { node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_2; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_2_FETCH_FROM_MEM; node->node.GROUP0.B2R2_ACK |= B2R2_ACK_SWAP_FG_BG; set_src(&node->node.GROUP4, bg->addr, bg); } else { node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_1; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_1_FETCH_FROM_MEM; set_src(&node->node.GROUP3, bg->addr, bg); } break; default: /* Should never, ever happen */ BUG_ON(1); break; } } /** * configure_dst() - configures the destination registers of the given node * * @node - the node to configure * @ivmx - the iVMX to use for color conversion * @dst - the destination buffer * * This operation will consume as many nodes as are required to write the * destination format. */ static int configure_dst(struct b2r2_node *node, struct b2r2_node_split_buf *dst, const u32 *ivmx, struct b2r2_node **next) { int ret; int nbr_planes = 1; int i; struct b2r2_node_split_buf dst_planes[3]; b2r2_log_info("%s: dst.win=(%d, %d, %d, %d)\n", __func__, dst->win.x, dst->win.y, dst->win.width, dst->win.height); memcpy(&dst_planes[0], dst, sizeof(dst_planes[0])); if (dst->type != B2R2_FMT_TYPE_RASTER) { /* There will be at least 2 planes */ nbr_planes = 2; memcpy(&dst_planes[1], dst, sizeof(dst_planes[1])); dst_planes[1].addr = dst->chroma_addr; dst_planes[1].plane_selection = B2R2_TTY_CHROMA_NOT_LUMA; if (!is_yuv444_fmt(dst->fmt)) { /* Horizontal resolution is half */ dst_planes[1].win.x /= 2; /* * Must round up the chroma size to handle cases when * luma size is not divisible by 2. E.g. luma width==7 r * equires chroma width==4. Chroma width==7/2==3 is only * enough for luma width==6. */ dst_planes[1].win.width = (dst_planes[1].win.width + 1) / 2; /* * If the buffer is in YUV420 format, the vertical * resolution is half as well. Height must be rounded in * the same way as is done for width. */ if (is_yuv420_fmt(dst->fmt)) { dst_planes[1].win.y /= 2; dst_planes[1].win.height = (dst_planes[1].win.height + 1) / 2; } } if (dst->type == B2R2_FMT_TYPE_PLANAR) { /* There will be a third plane as well */ nbr_planes = 3; if (!is_yuv444_fmt(dst->fmt)) { /* The chroma planes have half the luma pitch */ dst_planes[1].pitch /= 2; } memcpy(&dst_planes[2], &dst_planes[1], sizeof(dst_planes[2])); dst_planes[2].addr = dst->chroma_cr_addr; /* * The third plane will be Cr. * The flag B2R2_TTY_CB_NOT_CR actually works * the other way around, i.e. as if it was * B2R2_TTY_CR_NOT_CB. */ dst_planes[2].chroma_selection = B2R2_TTY_CB_NOT_CR; } } /* Configure one node for each plane */ for (i = 0; i < nbr_planes; i++) { if (node == NULL) { b2r2_log_warn("%s: " "Internal error! Out of nodes!\n", __func__); ret = -ENOMEM; goto error; } /* * When writing chroma, there's no need to read the luma and * vice versa. */ if ((node->node.GROUP3.B2R2_STY & B2R2_NATIVE_YUV) && (nbr_planes > 1)) { if (i != 0) { node->node.GROUP4.B2R2_STY |= B2R2_S3TY_ENABLE_BLANK_ACCESS; } if (i != 1) { node->node.GROUP0.B2R2_INS &= ~B2R2_INS_SOURCE_2_FETCH_FROM_MEM; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_2_COLOR_FILL_REGISTER; } if (i != 2) { node->node.GROUP0.B2R2_INS &= ~B2R2_INS_SOURCE_1_FETCH_FROM_MEM; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_1_COLOR_FILL_REGISTER; } } else if ((node->node.GROUP3.B2R2_STY & (B2R2_NATIVE_YCBCR42X_MBN | B2R2_NATIVE_YCBCR42X_R2B)) && (nbr_planes > 1)) { if (i != 0) { node->node.GROUP4.B2R2_STY |= B2R2_S3TY_ENABLE_BLANK_ACCESS; } } set_target(node, dst_planes[i].addr, &dst_planes[i]); node = node->next; } /* Consume the nodes */ *next = node; return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * configure_blend() - configures the given node for alpha blending * * @node - the node to configure * @flags - the flags passed in the blt_request * @global_alpha - the global alpha to use (if enabled in flags) * * This operation will not consume any nodes. * * NOTE: This method should be called _AFTER_ the destination has been * configured. * * WARNING: Take care when using this with semi-planar or planar sources since * either S1 or S2 will be overwritten! */ static void configure_blend(struct b2r2_node *node, u32 flags, u32 global_alpha) { node->node.GROUP0.B2R2_ACK &= ~(B2R2_ACK_MODE_BYPASS_S2_S3); /* Check if the foreground is premultiplied */ if ((flags & B2R2_BLT_FLAG_SRC_IS_NOT_PREMULT) != 0) node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BLEND_NOT_PREMULT; else node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BLEND_PREMULT; /* Check if global alpha blend should be enabled */ if (flags & B2R2_BLT_FLAG_GLOBAL_ALPHA_BLEND) { /* B2R2 expects the global alpha to be in 0...128 range */ global_alpha = (global_alpha*128)/255; node->node.GROUP0.B2R2_ACK |= global_alpha << B2R2_ACK_GALPHA_ROPID_SHIFT; } else { node->node.GROUP0.B2R2_ACK |= (128 << B2R2_ACK_GALPHA_ROPID_SHIFT); } } /** * configure_clip() - configures destination clipping for the given node * * @node - the node to configure * @clip_rect - the clip rectangle * * This operation does not consume any nodes. */ static void configure_clip(struct b2r2_node *node, struct b2r2_blt_rect *clip_rect) { s32 l = clip_rect->x; s32 r = clip_rect->x + clip_rect->width - 1; s32 t = clip_rect->y; s32 b = clip_rect->y + clip_rect->height - 1; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_CLIP_WINDOW; node->node.GROUP0.B2R2_INS |= B2R2_INS_RECT_CLIP_ENABLED; /* Clip window setup */ node->node.GROUP6.B2R2_CWO = ((t & 0x7FFF) << B2R2_CWO_Y_SHIFT) | ((l & 0x7FFF) << B2R2_CWO_X_SHIFT); node->node.GROUP6.B2R2_CWS = ((b & 0x7FFF) << B2R2_CWO_Y_SHIFT) | ((r & 0x7FFF) << B2R2_CWO_X_SHIFT); } /** * set_buf() - configures the given buffer with the provided values * * @addr - the physical base address * @img - the blt image to base the buffer on * @rect - the rectangle to use * @color_fill - determines whether the buffer should be used for color fill * @color - the color to use in case of color fill */ static void set_buf(struct b2r2_node_split_buf *buf, u32 addr, const struct b2r2_blt_img *img, const struct b2r2_blt_rect *rect, bool color_fill, u32 color) { memset(buf, 0, sizeof(*buf)); buf->fmt = img->fmt; buf->type = get_fmt_type(img->fmt); if (color_fill) { buf->type = B2R2_FMT_TYPE_RASTER; buf->color = color; } else { buf->addr = addr; buf->alpha_range = get_alpha_range(img->fmt); if (img->pitch == 0) buf->pitch = fmt_byte_pitch(img->fmt, img->width); else buf->pitch = img->pitch; buf->height = img->height; buf->width = img->width; switch (buf->type) { case B2R2_FMT_TYPE_SEMI_PLANAR: buf->chroma_addr = (u32)(((u8 *)addr) + buf->pitch * buf->height); break; case B2R2_FMT_TYPE_PLANAR: if (is_yuv422_fmt(buf->fmt) || is_yuv420_fmt(buf->fmt)) { buf->chroma_addr = (u32)(((u8 *)addr) + buf->pitch * buf->height); } else { buf->chroma_cr_addr = (u32)(((u8 *)addr) + buf->pitch * buf->height); } if (is_yuv420_fmt(buf->fmt)) { /* * Use ceil(height/2) in case * buffer height is not divisible by 2. */ buf->chroma_cr_addr = (u32)(((u8 *)buf->chroma_addr) + (buf->pitch >> 1) * ((buf->height + 1) >> 1)); } else if (is_yuv422_fmt(buf->fmt)) { buf->chroma_cr_addr = (u32)(((u8 *)buf->chroma_addr) + (buf->pitch >> 1) * buf->height); } else if (is_yvu420_fmt(buf->fmt)) { buf->chroma_addr = (u32)(((u8 *)buf->chroma_cr_addr) + (buf->pitch >> 1) * ((buf->height + 1) >> 1)); } else if (is_yvu422_fmt(buf->fmt)) { buf->chroma_addr = (u32)(((u8 *)buf->chroma_cr_addr) + (buf->pitch >> 1) * buf->height); } break; default: break; } memcpy(&buf->rect, rect, sizeof(buf->rect)); } } /** * setup_tmp_buf() - configure a temporary buffer */ static int setup_tmp_buf(struct b2r2_node_split_buf *tmp, u32 max_size, enum b2r2_blt_fmt pref_fmt, u32 pref_width, u32 pref_height) { int ret; enum b2r2_blt_fmt fmt; u32 width; u32 height; u32 pitch; u32 size; /* Determine what format we should use for the tmp buf */ if (is_rgb_fmt(pref_fmt)) { fmt = B2R2_BLT_FMT_32_BIT_ARGB8888; } else if (is_bgr_fmt(pref_fmt)) { fmt = B2R2_BLT_FMT_32_BIT_ABGR8888; } else if (is_yvu_fmt(pref_fmt)) { fmt = B2R2_BLT_FMT_CB_Y_CR_Y; } else if (is_yuv_fmt(pref_fmt)) { fmt = B2R2_BLT_FMT_32_BIT_AYUV8888; } else { /* Wait, what? */ b2r2_log_warn("%s: " "Cannot create tmp buf from this fmt (%d)\n", __func__, pref_fmt); ret = -EINVAL; goto error; } /* See if we can fit the entire preferred rectangle */ width = pref_width; height = pref_height; pitch = fmt_byte_pitch(fmt, width); size = pitch * height; if (size > max_size) { /* We need to limit the size, so we choose a different width */ width = MIN(width, B2R2_RESCALE_MAX_WIDTH); pitch = fmt_byte_pitch(fmt, width); height = MIN(height, max_size / pitch); size = pitch * height; } /* We should at least have enough room for one scanline */ if (height == 0) { b2r2_log_warn("%s: Not enough tmp mem!\n", __func__); ret = -ENOMEM; goto error; } memset(tmp, 0, sizeof(*tmp)); tmp->fmt = fmt; tmp->type = B2R2_FMT_TYPE_RASTER; tmp->height = height; tmp->width = width; tmp->pitch = pitch; tmp->rect.width = width; tmp->rect.height = tmp->height; tmp->alpha_range = B2R2_TY_ALPHA_RANGE_255; return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * get_alpha_range() - returns the alpha range of the given format */ static enum b2r2_ty get_alpha_range(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_24_BIT_ARGB8565: case B2R2_BLT_FMT_32_BIT_ARGB8888: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_32_BIT_VUYA8888: case B2R2_BLT_FMT_8_BIT_A8: case B2R2_BLT_FMT_32_BIT_ABGR8888: return B2R2_TY_ALPHA_RANGE_255; /* 0 - 255 */ default: return B2R2_TY_ALPHA_RANGE_128; /* 0 - 128 */ } } /** * get_alpha() - returns the pixel alpha in 0...255 range */ static u8 get_alpha(enum b2r2_blt_fmt fmt, u32 pixel) { switch (fmt) { case B2R2_BLT_FMT_32_BIT_ARGB8888: case B2R2_BLT_FMT_32_BIT_ABGR8888: case B2R2_BLT_FMT_32_BIT_AYUV8888: return (pixel >> 24) & 0xff; case B2R2_BLT_FMT_32_BIT_VUYA8888: return pixel & 0xff; case B2R2_BLT_FMT_24_BIT_ARGB8565: return (pixel & 0xfff) >> 16; case B2R2_BLT_FMT_16_BIT_ARGB4444: return (((pixel >> 12) & 0xf) * 255) / 15; case B2R2_BLT_FMT_16_BIT_ARGB1555: return (pixel >> 15) * 255; case B2R2_BLT_FMT_1_BIT_A1: return pixel * 255; case B2R2_BLT_FMT_8_BIT_A8: return pixel; default: return 255; } } /** * set_alpha() - returns a color value with the alpha component set */ static u32 set_alpha(enum b2r2_blt_fmt fmt, u8 alpha, u32 color) { u32 alpha_mask; switch (fmt) { case B2R2_BLT_FMT_32_BIT_ARGB8888: case B2R2_BLT_FMT_32_BIT_ABGR8888: case B2R2_BLT_FMT_32_BIT_AYUV8888: color &= 0x00ffffff; alpha_mask = alpha << 24; break; case B2R2_BLT_FMT_32_BIT_VUYA8888: color &= 0xffffff00; alpha_mask = alpha; break; case B2R2_BLT_FMT_24_BIT_ARGB8565: color &= 0x00ffff; alpha_mask = alpha << 16; break; case B2R2_BLT_FMT_16_BIT_ARGB4444: color &= 0x0fff; alpha_mask = (alpha << 8) & 0xF000; break; case B2R2_BLT_FMT_16_BIT_ARGB1555: color &= 0x7fff; alpha_mask = (alpha / 255) << 15 ; break; case B2R2_BLT_FMT_1_BIT_A1: color = 0; alpha_mask = (alpha / 255); break; case B2R2_BLT_FMT_8_BIT_A8: color = 0; alpha_mask = alpha; break; default: alpha_mask = 0; } return color | alpha_mask; } /** * fmt_has_alpha() - returns whether the given format carries an alpha value */ static bool fmt_has_alpha(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_16_BIT_ARGB4444: case B2R2_BLT_FMT_16_BIT_ARGB1555: case B2R2_BLT_FMT_32_BIT_ARGB8888: case B2R2_BLT_FMT_32_BIT_ABGR8888: case B2R2_BLT_FMT_24_BIT_ARGB8565: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_32_BIT_VUYA8888: case B2R2_BLT_FMT_1_BIT_A1: case B2R2_BLT_FMT_8_BIT_A8: return true; default: return false; } } /** * is_rgb_fmt() - returns whether the given format is a rgb format */ static bool is_rgb_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_16_BIT_ARGB4444: case B2R2_BLT_FMT_16_BIT_ARGB1555: case B2R2_BLT_FMT_16_BIT_RGB565: case B2R2_BLT_FMT_24_BIT_RGB888: case B2R2_BLT_FMT_32_BIT_ARGB8888: case B2R2_BLT_FMT_24_BIT_ARGB8565: case B2R2_BLT_FMT_1_BIT_A1: case B2R2_BLT_FMT_8_BIT_A8: return true; default: return false; } } /** * is_bgr_fmt() - returns whether the given format is a bgr format */ static bool is_bgr_fmt(enum b2r2_blt_fmt fmt) { return (fmt == B2R2_BLT_FMT_32_BIT_ABGR8888); } /** * is_yuv_fmt() - returns whether the given format is a yuv format */ static bool is_yuv_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_24_BIT_YUV888: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_24_BIT_VUY888: case B2R2_BLT_FMT_32_BIT_VUYA8888: case B2R2_BLT_FMT_Y_CB_Y_CR: case B2R2_BLT_FMT_CB_Y_CR_Y: case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE: case B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE: case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: return true; default: return false; } } /** * is_yvu_fmt() - returns whether the given format is a yvu format */ static bool is_yvu_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_Y_CB_Y_CR: case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: return true; default: return false; } } /** * is_yuv420_fmt() - returns whether the given format is a yuv420 format */ static bool is_yuv420_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE: return true; default: return false; } } static bool is_yuv422_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_Y_CB_Y_CR: case B2R2_BLT_FMT_CB_Y_CR_Y: case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE: return true; default: return false; } } /** * is_yvu420_fmt() - returns whether the given format is a yvu420 format */ static bool is_yvu420_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: return true; default: return false; } } static bool is_yvu422_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_CB_Y_CR_Y: case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: return true; default: return false; } } /** * is_yuv444_fmt() - returns whether the given format is a yuv444 format */ static bool is_yuv444_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_24_BIT_YUV888: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_24_BIT_VUY888: case B2R2_BLT_FMT_32_BIT_VUYA8888: case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: return true; default: return false; } } /** * get_fmt_byte_pitch() - returns the pitch of a pixmap with the given width */ static int fmt_byte_pitch(enum b2r2_blt_fmt fmt, u32 width) { int pitch; switch (fmt) { case B2R2_BLT_FMT_1_BIT_A1: pitch = width >> 3; /* Shift is faster than division */ if ((width & 0x3) != 0) /* Check for remainder */ pitch++; return pitch; case B2R2_BLT_FMT_8_BIT_A8: /* Fall through */ case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: /* Fall through */ case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: /* Fall through */ case B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE: /* Fall through */ case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: /* Fall through */ case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: /* Fall through */ case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: /* Fall through */ case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: /* Fall through */ case B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE: /* Fall through */ case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: /* Fall through */ case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: /* Fall through */ case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: return width; case B2R2_BLT_FMT_16_BIT_ARGB4444: /* Fall through */ case B2R2_BLT_FMT_16_BIT_ARGB1555: /* Fall through */ case B2R2_BLT_FMT_16_BIT_RGB565: /* Fall through */ case B2R2_BLT_FMT_Y_CB_Y_CR: /* Fall through */ case B2R2_BLT_FMT_CB_Y_CR_Y: return width << 1; case B2R2_BLT_FMT_24_BIT_RGB888: /* Fall through */ case B2R2_BLT_FMT_24_BIT_ARGB8565: /* Fall through */ case B2R2_BLT_FMT_24_BIT_YUV888: /* Fall through */ case B2R2_BLT_FMT_24_BIT_VUY888: return width * 3; case B2R2_BLT_FMT_32_BIT_ARGB8888: /* Fall through */ case B2R2_BLT_FMT_32_BIT_ABGR8888: /* Fall through */ case B2R2_BLT_FMT_32_BIT_AYUV8888: /* Fall through */ case B2R2_BLT_FMT_32_BIT_VUYA8888: return width << 2; default: /* Should never, ever happen */ BUG_ON(1); return 0; } } /** * to_native_fmt() - returns the native B2R2 format */ static enum b2r2_native_fmt to_native_fmt(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_UNUSED: return B2R2_NATIVE_RGB565; case B2R2_BLT_FMT_1_BIT_A1: return B2R2_NATIVE_A1; case B2R2_BLT_FMT_8_BIT_A8: return B2R2_NATIVE_A8; case B2R2_BLT_FMT_16_BIT_RGB565: return B2R2_NATIVE_RGB565; case B2R2_BLT_FMT_16_BIT_ARGB4444: return B2R2_NATIVE_ARGB4444; case B2R2_BLT_FMT_16_BIT_ARGB1555: return B2R2_NATIVE_ARGB1555; case B2R2_BLT_FMT_24_BIT_ARGB8565: return B2R2_NATIVE_ARGB8565; case B2R2_BLT_FMT_24_BIT_RGB888: return B2R2_NATIVE_RGB888; case B2R2_BLT_FMT_24_BIT_YUV888: case B2R2_BLT_FMT_24_BIT_VUY888: /* Not actually supported by HW */ return B2R2_NATIVE_YCBCR888; case B2R2_BLT_FMT_32_BIT_ABGR8888: /* Not actually supported by HW */ case B2R2_BLT_FMT_32_BIT_ARGB8888: return B2R2_NATIVE_ARGB8888; case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_32_BIT_VUYA8888: /* Not actually supported by HW */ return B2R2_NATIVE_AYCBCR8888; case B2R2_BLT_FMT_CB_Y_CR_Y: return B2R2_NATIVE_YCBCR422R; case B2R2_BLT_FMT_Y_CB_Y_CR: return B2R2_NATIVE_YCBCR422R; case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: return B2R2_NATIVE_YCBCR42X_R2B; case B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE: case B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE: return B2R2_NATIVE_YCBCR42X_MBN; case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: return B2R2_NATIVE_YUV; default: /* Should never ever happen */ return B2R2_NATIVE_BYTE; } } /** * Bit-expand the color from fmt to RGB888 with blue at LSB. * Copy MSBs into missing LSBs. */ static u32 to_RGB888(u32 color, const enum b2r2_blt_fmt fmt) { u32 out_color = 0; u32 r = 0; u32 g = 0; u32 b = 0; switch (fmt) { case B2R2_BLT_FMT_16_BIT_ARGB4444: r = ((color & 0xf00) << 12) | ((color & 0xf00) << 8); g = ((color & 0xf0) << 8) | ((color & 0xf0) << 4); b = ((color & 0xf) << 4) | (color & 0xf); out_color = r | g | b; break; case B2R2_BLT_FMT_16_BIT_ARGB1555: r = ((color & 0x7c00) << 9) | ((color & 0x7000) << 4); g = ((color & 0x3e0) << 6) | ((color & 0x380) << 1); b = ((color & 0x1f) << 3) | ((color & 0x1c) >> 2); out_color = r | g | b; break; case B2R2_BLT_FMT_16_BIT_RGB565: r = ((color & 0xf800) << 8) | ((color & 0xe000) << 3); g = ((color & 0x7e0) << 5) | ((color & 0x600) >> 1); b = ((color & 0x1f) << 3) | ((color & 0x1c) >> 2); out_color = r | g | b; break; case B2R2_BLT_FMT_24_BIT_RGB888: case B2R2_BLT_FMT_32_BIT_ARGB8888: out_color = color & 0xffffff; break; case B2R2_BLT_FMT_32_BIT_ABGR8888: r = (color & 0xff) << 16; g = color & 0xff00; b = (color & 0xff0000) >> 16; out_color = r | g | b; break; case B2R2_BLT_FMT_24_BIT_ARGB8565: r = ((color & 0xf800) << 8) | ((color & 0xe000) << 3); g = ((color & 0x7e0) << 5) | ((color & 0x600) >> 1); b = ((color & 0x1f) << 3) | ((color & 0x1c) >> 2); out_color = r | g | b; break; default: break; } return out_color; } /** * get_fmt_type() - returns the type of the given format (raster, planar, etc.) */ static enum b2r2_fmt_type get_fmt_type(enum b2r2_blt_fmt fmt) { switch (fmt) { case B2R2_BLT_FMT_16_BIT_ARGB4444: case B2R2_BLT_FMT_16_BIT_ARGB1555: case B2R2_BLT_FMT_16_BIT_RGB565: case B2R2_BLT_FMT_24_BIT_RGB888: case B2R2_BLT_FMT_32_BIT_ARGB8888: case B2R2_BLT_FMT_Y_CB_Y_CR: case B2R2_BLT_FMT_CB_Y_CR_Y: case B2R2_BLT_FMT_32_BIT_ABGR8888: case B2R2_BLT_FMT_24_BIT_ARGB8565: case B2R2_BLT_FMT_24_BIT_YUV888: case B2R2_BLT_FMT_32_BIT_AYUV8888: case B2R2_BLT_FMT_24_BIT_VUY888: case B2R2_BLT_FMT_32_BIT_VUYA8888: case B2R2_BLT_FMT_1_BIT_A1: case B2R2_BLT_FMT_8_BIT_A8: return B2R2_FMT_TYPE_RASTER; case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: return B2R2_FMT_TYPE_PLANAR; case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: case B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE: case B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE: return B2R2_FMT_TYPE_SEMI_PLANAR; default: return B2R2_FMT_TYPE_RASTER; } } /** * is_transform() - returns whether the given request is a transform operation */ static bool is_transform(const struct b2r2_blt_request *req) { return (req->user_req.transform != B2R2_BLT_TRANSFORM_NONE) || (req->user_req.src_rect.width != req->user_req.dst_rect.width) || (req->user_req.src_rect.height != req->user_req.dst_rect.height); } /** * calculate_scale_factor() - calculates the scale factor between the given * values */ static int calculate_scale_factor(u32 from, u32 to, u16 *sf_out) { int ret; u32 sf; b2r2_log_info("%s\n", __func__); if (to == from) { *sf_out = 1 << 10; return 0; } else if (to == 0) { b2r2_log_err("%s: To is 0!\n", __func__); BUG_ON(1); } sf = (from << 10) / to; if ((sf & 0xffff0000) != 0) { /* Overflow error */ b2r2_log_warn("%s: " "Scale factor too large\n", __func__); ret = -EINVAL; goto error; } else if (sf == 0) { b2r2_log_warn("%s: " "Scale factor too small\n", __func__); ret = -EINVAL; goto error; } *sf_out = (u16)sf; b2r2_log_info("%s exit\n", __func__); return 0; error: b2r2_log_warn("%s: Exit...\n", __func__); return ret; } /** * rescale() - rescales the given dimension * * Returns the rescaled dimension in 22.10 fixed point format. */ static s32 rescale(s32 dim, u16 sf) { b2r2_log_info("%s\n", __func__); if (sf == 0) { b2r2_log_err("%s: Scale factor is 0!\n", __func__); BUG_ON(1); } /* * This is normally not safe to do, since it drastically decreases the * precision of the integer part of the dimension. But since the B2R2 * hardware only has 12-bit registers for these values, we are safe. */ return (dim << 20) / sf; } /** * inv_rescale() - does an inverted rescale of the given dimension * * Returns the rescaled dimension in 22.10 fixed point format. */ static s32 inv_rescale(s32 dim, u16 sf) { if (sf == 0) return dim; return dim * sf; } /** * set_target() - sets the target registers of the given node */ static void set_target(struct b2r2_node *node, u32 addr, struct b2r2_node_split_buf *buf) { s32 l; s32 r; s32 t; s32 b; if (buf->tmp_buf_index) node->dst_tmp_index = buf->tmp_buf_index; node->node.GROUP1.B2R2_TBA = addr; node->node.GROUP1.B2R2_TTY = buf->pitch | to_native_fmt(buf->fmt) | buf->alpha_range | buf->chroma_selection | buf->hso | buf->vso | buf->dither | buf->plane_selection; if (buf->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || buf->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) node->node.GROUP1.B2R2_TTY |= B2R2_TY_ENDIAN_BIG_NOT_LITTLE; node->node.GROUP1.B2R2_TSZ = ((buf->win.width & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | ((buf->win.height & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); node->node.GROUP1.B2R2_TXY = ((buf->win.x & 0xffff) << B2R2_XY_X_SHIFT) | ((buf->win.y & 0xffff) << B2R2_XY_Y_SHIFT); /* Check if the rectangle is outside the buffer */ if (buf->vso == B2R2_TY_VSO_BOTTOM_TO_TOP) t = buf->win.y - (buf->win.height - 1); else t = buf->win.y; if (buf->hso == B2R2_TY_HSO_RIGHT_TO_LEFT) l = buf->win.x - (buf->win.width - 1); else l = buf->win.x; r = l + buf->win.width; b = t + buf->win.height; /* Clip to the destination buffer to prevent memory overwrites */ if ((l < 0) || (r > buf->width) || (t < 0) || (b > buf->height)) { /* The clip rectangle is including the borders */ l = MAX(l, 0); r = MIN(r, buf->width) - 1; t = MAX(t, 0); b = MIN(b, buf->height) - 1; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_CLIP_WINDOW; node->node.GROUP0.B2R2_INS |= B2R2_INS_RECT_CLIP_ENABLED; node->node.GROUP6.B2R2_CWO = ((l & 0x7FFF) << B2R2_CWS_X_SHIFT) | ((t & 0x7FFF) << B2R2_CWS_Y_SHIFT); node->node.GROUP6.B2R2_CWS = ((r & 0x7FFF) << B2R2_CWO_X_SHIFT) | ((b & 0x7FFF) << B2R2_CWO_Y_SHIFT); } } /** * set_src() - configures the given source register with the given values */ static void set_src(struct b2r2_src_config *src, u32 addr, struct b2r2_node_split_buf *buf) { src->B2R2_SBA = addr; src->B2R2_STY = buf->pitch | to_native_fmt(buf->fmt) | buf->alpha_range | buf->hso | buf->vso; if (buf->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || buf->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) src->B2R2_STY |= B2R2_TY_ENDIAN_BIG_NOT_LITTLE; src->B2R2_SSZ = ((buf->win.width & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | ((buf->win.height & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); src->B2R2_SXY = ((buf->win.x & 0xffff) << B2R2_XY_X_SHIFT) | ((buf->win.y & 0xffff) << B2R2_XY_Y_SHIFT); } /** * set_src_1() - sets the source 1 registers of the given node */ static void set_src_1(struct b2r2_node *node, u32 addr, struct b2r2_node_split_buf *buf) { if (buf->tmp_buf_index) node->src_tmp_index = buf->tmp_buf_index; node->src_index = 1; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_1; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_1_FETCH_FROM_MEM; node->node.GROUP3.B2R2_SBA = addr; node->node.GROUP3.B2R2_STY = buf->pitch | to_native_fmt(buf->fmt) | buf->alpha_range | buf->hso | buf->vso; if (buf->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || buf->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) node->node.GROUP3.B2R2_STY |= B2R2_TY_ENDIAN_BIG_NOT_LITTLE; node->node.GROUP3.B2R2_SXY = ((buf->win.x & 0xffff) << B2R2_XY_X_SHIFT) | ((buf->win.y & 0xffff) << B2R2_XY_Y_SHIFT); /* Source 1 has no size register */ } /** * set_src_2() - sets the source 2 registers of the given node */ static void set_src_2(struct b2r2_node *node, u32 addr, struct b2r2_node_split_buf *buf) { if (buf->tmp_buf_index) node->src_tmp_index = buf->tmp_buf_index; node->src_index = 2; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_2; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_2_FETCH_FROM_MEM; set_src(&node->node.GROUP4, addr, buf); } /** * set_src_3() - sets the source 3 registers of the given node */ static void set_src_3(struct b2r2_node *node, u32 addr, struct b2r2_node_split_buf *buf) { if (buf->tmp_buf_index) node->src_tmp_index = buf->tmp_buf_index; node->src_index = 3; node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_3; node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_3_FETCH_FROM_MEM; set_src(&node->node.GROUP5, addr, buf); } /** * set_ivmx() - configures the iVMX registers with the given values */ static void set_ivmx(struct b2r2_node *node, const u32 *vmx_values) { node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; node->node.GROUP15.B2R2_VMX0 = vmx_values[0]; node->node.GROUP15.B2R2_VMX1 = vmx_values[1]; node->node.GROUP15.B2R2_VMX2 = vmx_values[2]; node->node.GROUP15.B2R2_VMX3 = vmx_values[3]; } /** * reset_nodes() - clears the node list */ static void reset_nodes(struct b2r2_node *node) { while (node != NULL) { memset(&node->node, 0, sizeof(node->node)); node->src_tmp_index = 0; node->dst_tmp_index = 0; /* TODO: Implement support for short linked lists */ node->node.GROUP0.B2R2_CIC = 0x7ffff; if (node->next != NULL) node->node.GROUP0.B2R2_NIP = node->next->physical_address; node = node->next; } } int b2r2_node_split_init(void) { b2r2_filters_init(); return 0; } void b2r2_node_split_exit(void) { b2r2_filters_exit(); }