diff options
Diffstat (limited to 'drivers/video/b2r2/b2r2_generic.c')
-rw-r--r-- | drivers/video/b2r2/b2r2_generic.c | 3303 |
1 files changed, 3303 insertions, 0 deletions
diff --git a/drivers/video/b2r2/b2r2_generic.c b/drivers/video/b2r2/b2r2_generic.c new file mode 100644 index 00000000000..5941e39be91 --- /dev/null +++ b/drivers/video/b2r2/b2r2_generic.c @@ -0,0 +1,3303 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * ST-Ericsson B2R2 generic. Full coverage of user interface but + * non optimized implementation. For Fallback purposes. + * + * Author: Maciej Socha <maciej.socha@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/kernel.h> +#include <linux/debugfs.h> + +#include "b2r2_generic.h" +#include "b2r2_internal.h" +#include "b2r2_global.h" +#include "b2r2_debug.h" +#include "b2r2_filters.h" + +/* + * Debug printing + */ +#define B2R2_GENERIC_DEBUG_AREAS 0 +#define B2R2_GENERIC_DEBUG + +#define B2R2_GENERIC_WORK_BUF_WIDTH 16 +#define B2R2_GENERIC_WORK_BUF_HEIGHT 16 +#define B2R2_GENERIC_WORK_BUF_PITCH (16 * 4) +#define B2R2_GENERIC_WORK_BUF_FMT B2R2_NATIVE_ARGB8888 + +/* + * Private functions + */ + +/** + * reset_nodes() - clears the node list + */ +static void reset_nodes(struct b2r2_node *node) +{ + b2r2_log_info("%s ENTRY\n", __func__); + + while (node != NULL) { + memset(&(node->node), 0, sizeof(node->node)); + + /* TODO: Implement support for short linked lists */ + node->node.GROUP0.B2R2_CIC = 0x7fffc; + + if (node->next == NULL) + break; + + node->node.GROUP0.B2R2_NIP = node->next->physical_address; + + node = node->next; + } + b2r2_log_info("%s DONE\n", __func__); +} + +/** + * dump_nodes() - prints the node list + */ +static void dump_nodes(struct b2r2_node *first, bool dump_all) +{ + struct b2r2_node *node = first; + b2r2_log_info("%s ENTRY\n", __func__); + do { + b2r2_log_debug("\nNODE START:\n=============\n"); + b2r2_log_debug("B2R2_ACK: \t0x%.8x\n", + node->node.GROUP0.B2R2_ACK); + b2r2_log_debug("B2R2_INS: \t0x%.8x\n", + node->node.GROUP0.B2R2_INS); + b2r2_log_debug("B2R2_CIC: \t0x%.8x\n", + node->node.GROUP0.B2R2_CIC); + b2r2_log_debug("B2R2_NIP: \t0x%.8x\n", + node->node.GROUP0.B2R2_NIP); + + b2r2_log_debug("B2R2_TSZ: \t0x%.8x\n", + node->node.GROUP1.B2R2_TSZ); + b2r2_log_debug("B2R2_TXY: \t0x%.8x\n", + node->node.GROUP1.B2R2_TXY); + b2r2_log_debug("B2R2_TTY: \t0x%.8x\n", + node->node.GROUP1.B2R2_TTY); + b2r2_log_debug("B2R2_TBA: \t0x%.8x\n", + node->node.GROUP1.B2R2_TBA); + + b2r2_log_debug("B2R2_S2CF: \t0x%.8x\n", + node->node.GROUP2.B2R2_S2CF); + b2r2_log_debug("B2R2_S1CF: \t0x%.8x\n", + node->node.GROUP2.B2R2_S1CF); + + b2r2_log_debug("B2R2_S1SZ: \t0x%.8x\n", + node->node.GROUP3.B2R2_SSZ); + b2r2_log_debug("B2R2_S1XY: \t0x%.8x\n", + node->node.GROUP3.B2R2_SXY); + b2r2_log_debug("B2R2_S1TY: \t0x%.8x\n", + node->node.GROUP3.B2R2_STY); + b2r2_log_debug("B2R2_S1BA: \t0x%.8x\n", + node->node.GROUP3.B2R2_SBA); + + b2r2_log_debug("B2R2_S2SZ: \t0x%.8x\n", + node->node.GROUP4.B2R2_SSZ); + b2r2_log_debug("B2R2_S2XY: \t0x%.8x\n", + node->node.GROUP4.B2R2_SXY); + b2r2_log_debug("B2R2_S2TY: \t0x%.8x\n", + node->node.GROUP4.B2R2_STY); + b2r2_log_debug("B2R2_S2BA: \t0x%.8x\n", + node->node.GROUP4.B2R2_SBA); + + b2r2_log_debug("B2R2_S3SZ: \t0x%.8x\n", + node->node.GROUP5.B2R2_SSZ); + b2r2_log_debug("B2R2_S3XY: \t0x%.8x\n", + node->node.GROUP5.B2R2_SXY); + b2r2_log_debug("B2R2_S3TY: \t0x%.8x\n", + node->node.GROUP5.B2R2_STY); + b2r2_log_debug("B2R2_S3BA: \t0x%.8x\n", + node->node.GROUP5.B2R2_SBA); + + b2r2_log_debug("B2R2_CWS: \t0x%.8x\n", + node->node.GROUP6.B2R2_CWS); + b2r2_log_debug("B2R2_CWO: \t0x%.8x\n", + node->node.GROUP6.B2R2_CWO); + + b2r2_log_debug("B2R2_FCTL: \t0x%.8x\n", + node->node.GROUP8.B2R2_FCTL); + b2r2_log_debug("B2R2_RSF: \t0x%.8x\n", + node->node.GROUP9.B2R2_RSF); + b2r2_log_debug("B2R2_RZI: \t0x%.8x\n", + node->node.GROUP9.B2R2_RZI); + b2r2_log_debug("B2R2_HFP: \t0x%.8x\n", + node->node.GROUP9.B2R2_HFP); + b2r2_log_debug("B2R2_VFP: \t0x%.8x\n", + node->node.GROUP9.B2R2_VFP); + b2r2_log_debug("B2R2_LUMA_RSF: \t0x%.8x\n", + node->node.GROUP10.B2R2_RSF); + b2r2_log_debug("B2R2_LUMA_RZI: \t0x%.8x\n", + node->node.GROUP10.B2R2_RZI); + b2r2_log_debug("B2R2_LUMA_HFP: \t0x%.8x\n", + node->node.GROUP10.B2R2_HFP); + b2r2_log_debug("B2R2_LUMA_VFP: \t0x%.8x\n", + node->node.GROUP10.B2R2_VFP); + + + b2r2_log_debug("B2R2_IVMX0: \t0x%.8x\n", + node->node.GROUP15.B2R2_VMX0); + b2r2_log_debug("B2R2_IVMX1: \t0x%.8x\n", + node->node.GROUP15.B2R2_VMX1); + b2r2_log_debug("B2R2_IVMX2: \t0x%.8x\n", + node->node.GROUP15.B2R2_VMX2); + b2r2_log_debug("B2R2_IVMX3: \t0x%.8x\n", + node->node.GROUP15.B2R2_VMX3); + b2r2_log_debug("\n=============\nNODE END\n"); + + node = node->next; + } while (node != NULL && dump_all); + + b2r2_log_info("%s DONE\n", __func__); +} + +/** + * to_native_fmt() - returns the native B2R2 format + */ +static inline 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_VUY888: + case B2R2_BLT_FMT_24_BIT_YUV888: + 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_VUYA8888: /* fall through */ + case B2R2_BLT_FMT_32_BIT_AYUV8888: + 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_YUV422_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YVU420_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_YUV422_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU420_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; + } +} + +/** + * get_alpha_range() - returns the alpha range of the given format + */ +static inline 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 */ + break; + default: + break; + } + + return B2R2_TY_ALPHA_RANGE_128; /* 0 - 128 */ +} + +static unsigned int get_pitch(enum b2r2_blt_fmt format, u32 width) +{ + switch (format) { + case B2R2_BLT_FMT_1_BIT_A1: { + int pitch = width >> 3; + /* Check for remainder */ + if (width & 7) + pitch++; + return pitch; + break; + } + case B2R2_BLT_FMT_8_BIT_A8: + return width; + break; + case B2R2_BLT_FMT_16_BIT_RGB565: /* all 16 bits/pixel RGB formats */ + case B2R2_BLT_FMT_16_BIT_ARGB1555: + case B2R2_BLT_FMT_16_BIT_ARGB4444: + return width * 2; + break; + case B2R2_BLT_FMT_24_BIT_RGB888: /* all 24 bits/pixel raster formats */ + case B2R2_BLT_FMT_24_BIT_ARGB8565: + case B2R2_BLT_FMT_24_BIT_YUV888: + case B2R2_BLT_FMT_24_BIT_VUY888: + return width * 3; + break; + case B2R2_BLT_FMT_32_BIT_ARGB8888: /* all 32 bits/pixel formats */ + case B2R2_BLT_FMT_32_BIT_ABGR8888: + case B2R2_BLT_FMT_32_BIT_VUYA8888: + case B2R2_BLT_FMT_32_BIT_AYUV8888: + return width * 4; + break; + case B2R2_BLT_FMT_Y_CB_Y_CR: + case B2R2_BLT_FMT_CB_Y_CR_Y: + /* width of the buffer must be a multiple of 4 */ + if (width & 3) { + b2r2_log_warn("%s: Illegal width " + "for fmt=%#010x width=%d\n", __func__, + format, width); + return 0; + } + return width * 2; + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + return width; + break; + /* fall through, same pitch and pointers */ + case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: + /* width of the buffer must be a multiple of 2 */ + if (width & 1) { + b2r2_log_warn("%s: Illegal width " + "for fmt=%#010x width=%d\n", __func__, + format, width); + return 0; + } + /* + * return pitch of the Y-buffer. + * U and V pitch can be derived from it. + */ + return width; + break; + case B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE: + case B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE: + /* width of the buffer must be a multiple of 16. */ + if (width & 15) { + b2r2_log_warn("%s: Illegal width " + "for fmt=%#010x width=%d\n", __func__, + format, width); + return 0; + } + /* + * return pitch of the Y-buffer. + * U and V pitch can be derived from it. + */ + return width; + break; + default: + b2r2_log_warn("%s: Unable to determine pitch " + "for fmt=%#010x width=%d\n", __func__, + format, width); + return 0; + } +} + +static s32 validate_buf(const struct b2r2_blt_img *image, + const struct b2r2_resolved_buf *buf) +{ + u32 expect_buf_size; + u32 pitch; + + if (image->width <= 0 || image->height <= 0) { + b2r2_log_warn("%s: width=%d or height=%d negative.\n", __func__, + image->width, image->height); + return -EINVAL; + } + + if (image->pitch == 0) { + /* autodetect pitch based on format and width */ + pitch = get_pitch(image->fmt, image->width); + } else + pitch = image->pitch; + + expect_buf_size = pitch * image->height; + + if (pitch == 0) { + b2r2_log_warn("%s: Unable to detect pitch. " + "fmt=%#010x, width=%d\n", + __func__, + image->fmt, image->width); + return -EINVAL; + } + + /* format specific adjustments */ + switch (image->fmt) { + case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: + /* + * Use ceil(height/2) in case buffer height + * is not divisible by 2. + */ + expect_buf_size += + (pitch >> 1) * ((image->height + 1) >> 1) * 2; + break; + case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: + expect_buf_size += (pitch >> 1) * image->height * 2; + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + expect_buf_size += pitch * image->height * 2; + break; + case B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: + /* + * include space occupied by U and V data. + * U and V interleaved, half resolution, which makes + * the UV pitch equal to luma pitch. + * Use ceil(height/2) in case buffer height + * is not divisible by 2. + */ + expect_buf_size += pitch * ((image->height + 1) >> 1); + break; + case B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: + /* + * include space occupied by U and V data. + * U and V interleaved, half resolution, which makes + * the UV pitch equal to luma pitch. + */ + expect_buf_size += pitch * image->height; + break; + case B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE: + /* Height must be a multiple of 16 for macro-block format.*/ + if (image->height & 15) { + b2r2_log_warn("%s: Illegal height " + "for fmt=%#010x height=%d\n", __func__, + image->fmt, image->height); + return -EINVAL; + } + expect_buf_size += pitch * (image->height >> 1); + break; + case B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE: + /* Height must be a multiple of 16 for macro-block format.*/ + if (image->height & 15) { + b2r2_log_warn("%s: Illegal height " + "for fmt=%#010x height=%d\n", __func__, + image->fmt, image->height); + return -EINVAL; + } + expect_buf_size += pitch * image->height; + break; + default: + break; + } + + if (buf->file_len < expect_buf_size) { + b2r2_log_warn("%s: Invalid buffer size:\n" + "fmt=%#010x w=%d h=%d buf.len=%d expect_buf_size=%d\n", + __func__, + image->fmt, image->width, image->height, buf->file_len, + expect_buf_size); + return -EINVAL; + } + + if (image->buf.type == B2R2_BLT_PTR_VIRTUAL) { + b2r2_log_warn("%s: Virtual pointers not supported yet.\n", + __func__); + return -EINVAL; + } + return 0; +} + +/* + * 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; +} + + +static void setup_fill_input_stage(const struct b2r2_blt_request *req, + struct b2r2_node *node, + struct b2r2_work_buf *out_buf) +{ + enum b2r2_native_fmt fill_fmt = 0; + u32 src_color = req->user_req.src_color; + const struct b2r2_blt_img *dst_img = &(req->user_req.dst_img); + b2r2_log_info("%s ENTRY\n", __func__); + + /* Determine format in src_color */ + switch (dst_img->fmt) { + /* ARGB formats */ + 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_32_BIT_ABGR8888: + case B2R2_BLT_FMT_24_BIT_ARGB8565: + case B2R2_BLT_FMT_1_BIT_A1: + case B2R2_BLT_FMT_8_BIT_A8: + if ((req->user_req.flags & B2R2_BLT_FLAG_SOURCE_FILL) != 0) { + fill_fmt = B2R2_NATIVE_ARGB8888; + } else { + /* SOURCE_FILL_RAW */ + fill_fmt = to_native_fmt(dst_img->fmt); + if (dst_img->fmt == B2R2_BLT_FMT_32_BIT_ABGR8888) { + /* + * Color is read from a register, + * where it is stored in ABGR format. + * Set up IVMX. + */ + node->node.GROUP0.B2R2_INS |= + B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_RGB_TO_BGR; + } + } + break; + /* YUV formats */ + case B2R2_BLT_FMT_Y_CB_Y_CR: + case B2R2_BLT_FMT_CB_Y_CR_Y: + 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_YUV420_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YVU420_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: + if ((req->user_req.flags & B2R2_BLT_FLAG_SOURCE_FILL) != 0) { + fill_fmt = B2R2_NATIVE_AYCBCR8888; + /* + * Set up IVMX + * The destination format is in fact YUV, + * but the input stage stores the data in + * an intermediate buffer which is RGB. + * Hence the conversion from YUV to RGB. + * Format of the supplied src_color is + * B2R2_BLT_FMT_32_BIT_AYUV8888. + */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_BLT_YUV888_TO_RGB_601_VIDEO; + } else { + /* SOURCE_FILL_RAW */ + bool dst_yuv_planar = + B2R2_BLT_FMT_YUV420_PACKED_PLANAR == + dst_img->fmt || + B2R2_BLT_FMT_YUV422_PACKED_PLANAR == + dst_img->fmt || + B2R2_BLT_FMT_YVU420_PACKED_PLANAR == + dst_img->fmt || + B2R2_BLT_FMT_YVU422_PACKED_PLANAR == + dst_img->fmt || + B2R2_BLT_FMT_YUV444_PACKED_PLANAR == + dst_img->fmt; + + bool dst_yuv_semi_planar = + B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR == + dst_img->fmt || + B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR == + dst_img->fmt || + B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR == + dst_img->fmt || + B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR == + dst_img->fmt || + B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE == + dst_img->fmt || + B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE == + dst_img->fmt; + + if (dst_yuv_planar || dst_yuv_semi_planar) { + /* + * SOURCE_FILL_RAW cannot be supported + * with multi-buffer formats. + * Force a legal format to prevent B2R2 + * from misbehaving. + */ + fill_fmt = B2R2_NATIVE_AYCBCR8888; + } else { + fill_fmt = to_native_fmt(dst_img->fmt); + } + + switch (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: + node->node.GROUP0.B2R2_INS |= + B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_BLT_YUV888_TO_RGB_601_VIDEO; + /* + * Re-arrange the color components from + * VUY(A) to (A)YUV + */ + if (dst_img->fmt == + B2R2_BLT_FMT_24_BIT_VUY888) { + u32 Y = src_color & 0xff; + u32 U = src_color & 0xff00; + u32 V = src_color & 0xff0000; + src_color = (Y << 16) | U | (V >> 16); + } else if (dst_img->fmt == + B2R2_BLT_FMT_32_BIT_VUYA8888) { + u32 A = src_color & 0xff; + u32 Y = src_color & 0xff00; + u32 U = src_color & 0xff0000; + u32 V = src_color & 0xff000000; + src_color = (A << 24) | + (Y << 8) | + (U >> 8) | + (V >> 24); + } + break; + case B2R2_BLT_FMT_Y_CB_Y_CR: + /* + * Setup input VMX to convert YVU to + * RGB 601 VIDEO + * Chroma components are swapped so + * it is YVU and not YUV. + */ + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_YVU_TO_RGB_601_VIDEO; + break; + default: + /* + * Set up IVMX + * The destination format is in fact YUV, + * but the input stage stores the data in + * an intermediate buffer which is RGB. + * Hence the conversion from YUV to RGB. + */ + node->node.GROUP0.B2R2_INS |= + B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_YUV_TO_RGB_601_VIDEO; + break; + } + } + break; + default: + src_color = 0; + fill_fmt = B2R2_NATIVE_ARGB8888; + break; + } + + node->node.GROUP1.B2R2_TBA = out_buf->phys_addr; + node->node.GROUP1.B2R2_TTY = + (B2R2_GENERIC_WORK_BUF_PITCH << B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + /* Set color fill on SRC2 channel */ + node->node.GROUP4.B2R2_SBA = 0; + node->node.GROUP4.B2R2_STY = + (0 << B2R2_TY_BITMAP_PITCH_SHIFT) | + fill_fmt | + get_alpha_range(dst_img->fmt) | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_2_COLOR_FILL_REGISTER; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_COLOR_FILL; + node->node.GROUP2.B2R2_S2CF = src_color; + + node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BYPASS_S2_S3; + b2r2_log_info("%s DONE\n", __func__); +} + +static void setup_input_stage(const struct b2r2_blt_request *req, + struct b2r2_node *node, + struct b2r2_work_buf *out_buf) +{ + /* Horizontal and vertical scaling factors in 6.10 fixed point format */ + s32 h_scf = 1 << 10; + s32 v_scf = 1 << 10; + const struct b2r2_blt_rect *src_rect = &(req->user_req.src_rect); + const struct b2r2_blt_rect *dst_rect = &(req->user_req.dst_rect); + const struct b2r2_blt_img *src_img = &(req->user_req.src_img); + u32 src_pitch = 0; + /* horizontal and vertical scan order for out_buf */ + enum b2r2_ty dst_hso = B2R2_TY_HSO_LEFT_TO_RIGHT; + enum b2r2_ty dst_vso = B2R2_TY_VSO_TOP_TO_BOTTOM; + u32 endianness = 0; + u32 fctl = 0; + u32 rsf = 0; + u32 rzi = 0; + bool yuv_semi_planar = + src_img->fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE || + src_img->fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE; + + bool yuv_planar = + src_img->fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR; + + struct b2r2_filter_spec *hf; + struct b2r2_filter_spec *vf; + + bool use_h_filter = false; + bool use_v_filter = false; + + b2r2_log_info("%s ENTRY\n", __func__); + + if (((B2R2_BLT_FLAG_SOURCE_FILL | B2R2_BLT_FLAG_SOURCE_FILL_RAW) & + req->user_req.flags) != 0) { + setup_fill_input_stage(req, node, out_buf); + b2r2_log_info("%s DONE\n", __func__); + return; + } + + if (src_img->pitch == 0) { + /* Determine pitch based on format and width of the image. */ + src_pitch = get_pitch(src_img->fmt, src_img->width); + } else { + src_pitch = src_img->pitch; + } + + b2r2_log_info("%s transform=%#010x\n", + __func__, req->user_req.transform); + if (req->user_req.transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) { + h_scf = (src_rect->width << 10) / dst_rect->height; + v_scf = (src_rect->height << 10) / dst_rect->width; + } else { + h_scf = (src_rect->width << 10) / dst_rect->width; + v_scf = (src_rect->height << 10) / dst_rect->height; + } + + hf = b2r2_filter_find(h_scf); + vf = b2r2_filter_find(v_scf); + + use_h_filter = h_scf != (1 << 10); + use_v_filter = v_scf != (1 << 10); + + /* B2R2_BLT_FLAG_BLUR overrides any scaling filter. */ + if (req->user_req.flags & B2R2_BLT_FLAG_BLUR) { + use_h_filter = true; + use_v_filter = true; + hf = b2r2_filter_blur(); + vf = b2r2_filter_blur(); + } + + /* Configure horizontal rescale */ + if (h_scf != (1 << 10)) { + b2r2_log_info("%s: Scaling horizontally by 0x%.8x" + "\ns(%d, %d)->d(%d, %d)\n", __func__, + h_scf, src_rect->width, src_rect->height, + dst_rect->width, dst_rect->height); + } + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER; + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= h_scf << B2R2_RSF_HSRC_INC_SHIFT; + rzi |= B2R2_RZI_DEFAULT_HNB_REPEAT; + + /* Configure vertical rescale */ + if (v_scf != (1 << 10)) { + b2r2_log_info("%s: Scaling vertically by 0x%.8x" + "\ns(%d, %d)->d(%d, %d)\n", __func__, + v_scf, src_rect->width, src_rect->height, + dst_rect->width, dst_rect->height); + } + fctl |= B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= v_scf << B2R2_RSF_VSRC_INC_SHIFT; + rzi |= 2 << B2R2_RZI_VNB_REPEAT_SHIFT; + + node->node.GROUP0.B2R2_INS |= B2R2_INS_RESCALE2D_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_RESIZE_CHROMA; + + /* Adjustments that depend on the source format */ + switch (src_img->fmt) { + case B2R2_BLT_FMT_32_BIT_ABGR8888: + /* Set up IVMX */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + + node->node.GROUP15.B2R2_VMX0 = B2R2_VMX0_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX1 = B2R2_VMX1_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX2 = B2R2_VMX2_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX3 = B2R2_VMX3_RGB_TO_BGR; + break; + case B2R2_BLT_FMT_Y_CB_Y_CR: + /* + * Setup input VMX to convert YVU to RGB 601 VIDEO + * Chroma components are swapped so + * it is YVU and not YUV. + */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_YVU_TO_RGB_601_VIDEO; + break; + case B2R2_BLT_FMT_CB_Y_CR_Y: + /* Set up IVMX */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_YUV_TO_RGB_601_VIDEO; + break; + 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: + /* + * Set up IVMX. + * For B2R2_BLT_FMT_32_BIT_YUV888 and + * B2R2_BLT_FMT_32_BIT_AYUV8888 + * the color components are laid out in memory as V, U, Y, (A) + * with V at the first byte (due to little endian addressing). + * B2R2 expects them to be as U, Y, V, (A) + * with U at the first byte. + */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_BLT_YUV888_TO_RGB_601_VIDEO; + + /* + * Re-arrange color components from VUY(A) to (A)YUV + * for input VMX to work on them further. + */ + if (src_img->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || + src_img->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) + endianness = B2R2_TY_ENDIAN_BIG_NOT_LITTLE; + break; + case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YVU420_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: { + /* + * Luma handled in the same way + * for all YUV multi-buffer formats. + * Set luma rescale registers. + */ + u32 rsf_luma = 0; + u32 rzi_luma = 0; + + /* Set up IVMX */ + node->node.GROUP0.B2R2_INS |= + B2R2_INS_IVMX_ENABLED | B2R2_INS_RESCALE2D_ENABLED; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_IVMX | B2R2_CIC_RESIZE_LUMA; + + if (src_img->fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + src_img->fmt == + B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR) { + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_YVU_TO_RGB_601_VIDEO; + } else { + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_YUV_TO_RGB_601_VIDEO; + } + + fctl |= B2R2_FCTL_LUMA_HF2D_MODE_ENABLE_RESIZER | + B2R2_FCTL_LUMA_VF2D_MODE_ENABLE_RESIZER; + + if (use_h_filter && hf) { + fctl |= B2R2_FCTL_LUMA_HF2D_MODE_ENABLE_FILTER; + node->node.GROUP10.B2R2_HFP = hf->h_coeffs_phys_addr; + } + + if (use_v_filter && vf) { + fctl |= B2R2_FCTL_LUMA_VF2D_MODE_ENABLE_FILTER; + node->node.GROUP10.B2R2_VFP = vf->v_coeffs_phys_addr; + } + + rsf_luma |= h_scf << B2R2_RSF_HSRC_INC_SHIFT; + rzi_luma |= B2R2_RZI_DEFAULT_HNB_REPEAT; + + rsf_luma |= v_scf << B2R2_RSF_VSRC_INC_SHIFT; + rzi_luma |= 2 << B2R2_RZI_VNB_REPEAT_SHIFT; + + node->node.GROUP10.B2R2_RSF = rsf_luma; + node->node.GROUP10.B2R2_RZI = rzi_luma; + + switch (src_img->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: + /* + * Chrominance is always half the luminance size + * so chrominance resizer is always active. + */ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | + B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (h_scf >> 1) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= (v_scf >> 1) << B2R2_RSF_VSRC_INC_SHIFT; + /* Select suitable filter for chroma */ + hf = b2r2_filter_find(h_scf >> 1); + vf = b2r2_filter_find(v_scf >> 1); + use_h_filter = true; + use_v_filter = true; + break; + 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: + /* + * Chrominance is always half the luminance size + * only in horizontal direction. + */ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | + B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (h_scf >> 1) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= v_scf << B2R2_RSF_VSRC_INC_SHIFT; + /* Select suitable filter for chroma */ + hf = b2r2_filter_find(h_scf >> 1); + use_h_filter = true; + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + /* Chrominance is the same size as luminance.*/ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | + B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= h_scf << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= v_scf << B2R2_RSF_VSRC_INC_SHIFT; + /* Select suitable filter for chroma */ + hf = b2r2_filter_find(h_scf); + vf = b2r2_filter_find(v_scf); + use_h_filter = true; + use_v_filter = true; + break; + default: + break; + } + break; + } + default: + break; + } + + /* + * Set the filter control and rescale registers. + * GROUP9 registers are used for all single-buffer formats + * or for chroma in case of multi-buffer YUV formats. + * h/v_filter is now appropriately selected for chroma scaling, + * be it YUV multi-buffer, or single-buffer raster format. + * B2R2_BLT_FLAG_BLUR overrides any scaling filter. + */ + if (req->user_req.flags & B2R2_BLT_FLAG_BLUR) { + use_h_filter = true; + use_v_filter = true; + hf = b2r2_filter_blur(); + vf = b2r2_filter_blur(); + } + + if (use_h_filter && hf) { + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_COLOR_CHANNEL_FILTER; + node->node.GROUP9.B2R2_HFP = hf->h_coeffs_phys_addr; + } + + if (use_v_filter && vf) { + fctl |= B2R2_FCTL_VF2D_MODE_ENABLE_COLOR_CHANNEL_FILTER; + node->node.GROUP9.B2R2_VFP = vf->v_coeffs_phys_addr; + } + + node->node.GROUP8.B2R2_FCTL |= fctl; + node->node.GROUP9.B2R2_RSF |= rsf; + node->node.GROUP9.B2R2_RZI |= rzi; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_FILTER_CONTROL; + + /* + * Flip transform is done before potential rotation. + * This can be achieved with appropriate scan order. + * Transform stage will only do rotation. + */ + if (req->user_req.transform & B2R2_BLT_TRANSFORM_FLIP_H) + dst_hso = B2R2_TY_HSO_RIGHT_TO_LEFT; + + if (req->user_req.transform & B2R2_BLT_TRANSFORM_FLIP_V) + dst_vso = B2R2_TY_VSO_BOTTOM_TO_TOP; + + /* Set target buffer */ + node->node.GROUP1.B2R2_TBA = out_buf->phys_addr; + node->node.GROUP1.B2R2_TTY = + (B2R2_GENERIC_WORK_BUF_PITCH << B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + dst_hso | dst_vso; + + if (yuv_planar) { + /* + * Set up chrominance buffers on source 1 and 2, + * luminance on source 3. + * src_pitch and physical_address apply to luminance, + * corresponding chrominance values have to be derived. + */ + u32 cb_addr = 0; + u32 cr_addr = 0; + u32 chroma_pitch = 0; + bool swapped_chroma = + src_img->fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + src_img->fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR; + enum b2r2_native_fmt src_fmt = to_native_fmt(src_img->fmt); + + if (swapped_chroma) + cr_addr = req->src_resolved.physical_address + + src_pitch * src_img->height; + else + cb_addr = req->src_resolved.physical_address + + src_pitch * src_img->height; + + switch (src_img->fmt) { + case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: + chroma_pitch = src_pitch >> 1; + if (swapped_chroma) + cb_addr = cr_addr + chroma_pitch * + (src_img->height >> 1); + else + cr_addr = cb_addr + chroma_pitch * + (src_img->height >> 1); + break; + case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: + chroma_pitch = src_pitch >> 1; + if (swapped_chroma) + cb_addr = cr_addr + chroma_pitch * + src_img->height; + else + cr_addr = cb_addr + chroma_pitch * + src_img->height; + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + /* Chrominance has full resolution, same as luminance.*/ + chroma_pitch = src_pitch; + cr_addr = + cb_addr + chroma_pitch * src_img->height; + break; + default: + break; + } + + node->node.GROUP3.B2R2_SBA = cr_addr; + node->node.GROUP3.B2R2_STY = + (chroma_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + src_fmt | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP4.B2R2_SBA = cb_addr; + node->node.GROUP4.B2R2_STY = node->node.GROUP3.B2R2_STY; + + node->node.GROUP5.B2R2_SBA = req->src_resolved.physical_address; + node->node.GROUP5.B2R2_STY = + (src_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + src_fmt | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_1_FETCH_FROM_MEM | + B2R2_INS_SOURCE_2_FETCH_FROM_MEM | + B2R2_INS_SOURCE_3_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_SOURCE_1 | + B2R2_CIC_SOURCE_2 | + B2R2_CIC_SOURCE_3; + } else if (yuv_semi_planar) { + /* + * Set up chrominance buffer on source 2, luminance on source 3. + * src_pitch and physical_address apply to luminance, + * corresponding chrominance values have to be derived. + * U and V are interleaved at half the luminance resolution, + * which makes the pitch of the UV plane equal + * to luminance pitch. + */ + u32 chroma_addr = req->src_resolved.physical_address + + src_pitch * src_img->height; + u32 chroma_pitch = src_pitch; + + enum b2r2_native_fmt src_fmt = to_native_fmt(src_img->fmt); + + node->node.GROUP4.B2R2_SBA = chroma_addr; + node->node.GROUP4.B2R2_STY = + (chroma_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + src_fmt | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP5.B2R2_SBA = req->src_resolved.physical_address; + node->node.GROUP5.B2R2_STY = + (src_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + src_fmt | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_2_FETCH_FROM_MEM | + B2R2_INS_SOURCE_3_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_SOURCE_2 | B2R2_CIC_SOURCE_3; + } else { + /* single buffer format */ + node->node.GROUP4.B2R2_SBA = req->src_resolved.physical_address; + node->node.GROUP4.B2R2_STY = + (src_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + to_native_fmt(src_img->fmt) | + get_alpha_range(src_img->fmt) | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM | + endianness; + + node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_2_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_2; + } + + if ((req->user_req.flags & + B2R2_BLT_FLAG_CLUT_COLOR_CORRECTION) != 0) { + 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 = req->clut_phys_addr; + } + + node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BYPASS_S2_S3; + + b2r2_log_info("%s DONE\n", __func__); +} + +static void setup_transform_stage(const struct b2r2_blt_request *req, + struct b2r2_node *node, + struct b2r2_work_buf *out_buf, + struct b2r2_work_buf *in_buf) +{ + /* vertical scan order for out_buf */ + enum b2r2_ty dst_vso = B2R2_TY_VSO_TOP_TO_BOTTOM; + enum b2r2_blt_transform transform = req->user_req.transform; + + b2r2_log_info("%s ENTRY\n", __func__); + + if (transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) { + /* + * Scan order must be flipped otherwise contents will + * be mirrored vertically. Leftmost column of in_buf + * would become top instead of bottom row of out_buf. + */ + dst_vso = B2R2_TY_VSO_BOTTOM_TO_TOP; + node->node.GROUP0.B2R2_INS |= B2R2_INS_ROTATION_ENABLED; + } + + /* Set target buffer */ + node->node.GROUP1.B2R2_TBA = out_buf->phys_addr; + node->node.GROUP1.B2R2_TTY = + (B2R2_GENERIC_WORK_BUF_PITCH << B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | dst_vso; + + /* Set source buffer on SRC2 channel */ + node->node.GROUP4.B2R2_SBA = in_buf->phys_addr; + node->node.GROUP4.B2R2_STY = + (B2R2_GENERIC_WORK_BUF_PITCH << B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP0.B2R2_INS |= B2R2_INS_SOURCE_2_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_2; + node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BYPASS_S2_S3; + + b2r2_log_info("%s DONE\n", __func__); +} + +/* +static void setup_mask_stage(const struct b2r2_blt_request req, + struct b2r2_node *node, + struct b2r2_work_buf *out_buf, + struct b2r2_work_buf *in_buf); +*/ + +static void setup_dst_read_stage(const struct b2r2_blt_request *req, + struct b2r2_node *node, + struct b2r2_work_buf *out_buf) +{ + const struct b2r2_blt_img *dst_img = &(req->user_req.dst_img); + u32 fctl = 0; + u32 rsf = 0; + u32 endianness = 0; + bool yuv_semi_planar = + dst_img->fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE || + dst_img->fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE; + + bool yuv_planar = + dst_img->fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR; + + u32 dst_pitch = 0; + if (dst_img->pitch == 0) { + /* Determine pitch based on format and width of the image. */ + dst_pitch = get_pitch(dst_img->fmt, dst_img->width); + } else { + dst_pitch = dst_img->pitch; + } + + b2r2_log_info("%s ENTRY\n", __func__); + + /* Adjustments that depend on the destination format */ + switch (dst_img->fmt) { + case B2R2_BLT_FMT_32_BIT_ABGR8888: + /* Set up IVMX */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + + node->node.GROUP15.B2R2_VMX0 = B2R2_VMX0_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX1 = B2R2_VMX1_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX2 = B2R2_VMX2_RGB_TO_BGR; + node->node.GROUP15.B2R2_VMX3 = B2R2_VMX3_RGB_TO_BGR; + break; + case B2R2_BLT_FMT_Y_CB_Y_CR: + /* Set up IVMX */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + /* + * Setup input VMX to convert YVU to RGB 601 VIDEO + * Chroma components are swapped + * so it is YVU and not YUV. + */ + node->node.GROUP15.B2R2_VMX0 = B2R2_VMX0_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = B2R2_VMX1_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = B2R2_VMX2_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = B2R2_VMX3_YVU_TO_RGB_601_VIDEO; + break; + case B2R2_BLT_FMT_CB_Y_CR_Y: + /* Set up IVMX */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = B2R2_VMX0_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = B2R2_VMX1_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = B2R2_VMX2_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = B2R2_VMX3_YUV_TO_RGB_601_VIDEO; + break; + 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: + /* + * Set up IVMX. + * For B2R2_BLT_FMT_32_BIT_YUV888 and + * B2R2_BLT_FMT_32_BIT_AYUV8888 + * the color components are laid out in memory as V, U, Y, (A) + * with V at the first byte (due to little endian addressing). + * B2R2 expects them to be as U, Y, V, (A) + * with U at the first byte. + */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_BLT_YUV888_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_BLT_YUV888_TO_RGB_601_VIDEO; + + /* + * Re-arrange color components from VUY(A) to (A)YUV + * for input VMX to work on them further. + */ + if (dst_img->fmt == B2R2_BLT_FMT_24_BIT_VUY888 || + dst_img->fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) + endianness = B2R2_TY_ENDIAN_BIG_NOT_LITTLE; + break; + case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + case B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR: + case B2R2_BLT_FMT_YVU420_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: { + /* Set up IVMX */ + node->node.GROUP0.B2R2_INS |= B2R2_INS_IVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_IVMX; + + if (dst_img->fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + dst_img->fmt == + B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR) { + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_YVU_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_YVU_TO_RGB_601_VIDEO; + } else { + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_YUV_TO_RGB_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_YUV_TO_RGB_601_VIDEO; + } + + switch (dst_img->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: + /* + * Chrominance is always half the luminance size + * so chrominance resizer is always active. + */ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | + B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (1 << 9) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= (1 << 9) << B2R2_RSF_VSRC_INC_SHIFT; + break; + 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: + /* + * Chrominance is always half the luminance size + * only in horizontal direction. + */ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (1 << 9) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= (1 << 10) << B2R2_RSF_VSRC_INC_SHIFT; + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + /* Chrominance is the same size as luminance.*/ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | + B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (1 << 10) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= (1 << 10) << B2R2_RSF_VSRC_INC_SHIFT; + break; + default: + break; + } + /* Set the filter control and rescale registers for chroma */ + node->node.GROUP8.B2R2_FCTL |= fctl; + node->node.GROUP9.B2R2_RSF |= rsf; + node->node.GROUP9.B2R2_RZI = + B2R2_RZI_DEFAULT_HNB_REPEAT | + (2 << B2R2_RZI_VNB_REPEAT_SHIFT); + node->node.GROUP0.B2R2_INS |= B2R2_INS_RESCALE2D_ENABLED; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_FILTER_CONTROL | B2R2_CIC_RESIZE_CHROMA; + break; + } + default: + break; + } + + /* Set target buffer */ + node->node.GROUP1.B2R2_TBA = out_buf->phys_addr; + node->node.GROUP1.B2R2_TTY = + (B2R2_GENERIC_WORK_BUF_PITCH << B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + if (yuv_planar) { + /* + * Set up chrominance buffers on source 1 and 2, + * luminance on source 3. + * dst_pitch and physical_address apply to luminance, + * corresponding chrominance values have to be derived. + */ + u32 cb_addr = 0; + u32 cr_addr = 0; + u32 chroma_pitch = 0; + bool swapped_chroma = + dst_img->fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + dst_img->fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR; + enum b2r2_native_fmt dst_native_fmt = + to_native_fmt(dst_img->fmt); + + if (swapped_chroma) + cr_addr = req->dst_resolved.physical_address + + dst_pitch * dst_img->height; + else + cb_addr = req->dst_resolved.physical_address + + dst_pitch * dst_img->height; + + switch (dst_img->fmt) { + case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: + chroma_pitch = dst_pitch >> 1; + if (swapped_chroma) + cb_addr = cr_addr + chroma_pitch * + (dst_img->height >> 1); + else + cr_addr = cb_addr + chroma_pitch * + (dst_img->height >> 1); + break; + case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: + chroma_pitch = dst_pitch >> 1; + if (swapped_chroma) + cb_addr = cr_addr + chroma_pitch * + dst_img->height; + else + cr_addr = cb_addr + chroma_pitch * + dst_img->height; + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + chroma_pitch = dst_pitch; + cr_addr = + cb_addr + chroma_pitch * dst_img->height; + break; + default: + break; + } + + node->node.GROUP3.B2R2_SBA = cr_addr; + node->node.GROUP3.B2R2_STY = + (chroma_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP4.B2R2_SBA = cb_addr; + node->node.GROUP4.B2R2_STY = node->node.GROUP3.B2R2_STY; + + node->node.GROUP5.B2R2_SBA = req->dst_resolved.physical_address; + node->node.GROUP5.B2R2_STY = + (dst_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_1_FETCH_FROM_MEM | + B2R2_INS_SOURCE_2_FETCH_FROM_MEM | + B2R2_INS_SOURCE_3_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_SOURCE_1 | + B2R2_CIC_SOURCE_2 | + B2R2_CIC_SOURCE_3; + } else if (yuv_semi_planar) { + /* + * Set up chrominance buffer on source 2, luminance on source 3. + * dst_pitch and physical_address apply to luminance, + * corresponding chrominance values have to be derived. + * U and V are interleaved at half the luminance resolution, + * which makes the pitch of the UV plane equal + * to luminance pitch. + */ + u32 chroma_addr = req->dst_resolved.physical_address + + dst_pitch * dst_img->height; + u32 chroma_pitch = dst_pitch; + + enum b2r2_native_fmt dst_native_fmt = + to_native_fmt(dst_img->fmt); + + node->node.GROUP4.B2R2_SBA = chroma_addr; + node->node.GROUP4.B2R2_STY = + (chroma_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP5.B2R2_SBA = req->dst_resolved.physical_address; + node->node.GROUP5.B2R2_STY = + (dst_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_2_FETCH_FROM_MEM | + B2R2_INS_SOURCE_3_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_SOURCE_2 | B2R2_CIC_SOURCE_3; + } else { + /* single buffer format */ + node->node.GROUP4.B2R2_SBA = req->dst_resolved.physical_address; + node->node.GROUP4.B2R2_STY = + (dst_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + to_native_fmt(dst_img->fmt) | + get_alpha_range(dst_img->fmt) | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM | + endianness; + + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_2_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_2; + } + + node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BYPASS_S2_S3; + + b2r2_log_info("%s DONE\n", __func__); +} + +static void setup_blend_stage(const struct b2r2_blt_request *req, + struct b2r2_node *node, + struct b2r2_work_buf *bg_buf, + struct b2r2_work_buf *fg_buf) +{ + u32 global_alpha = req->user_req.global_alpha; + b2r2_log_info("%s ENTRY\n", __func__); + + node->node.GROUP0.B2R2_ACK = 0; + + if (req->user_req.flags & + (B2R2_BLT_FLAG_GLOBAL_ALPHA_BLEND | + B2R2_BLT_FLAG_PER_PIXEL_ALPHA_BLEND)) { + /* Some kind of blending needs to be done. */ + if (req->user_req.flags & B2R2_BLT_FLAG_SRC_IS_NOT_PREMULT) + node->node.GROUP0.B2R2_ACK |= + B2R2_ACK_MODE_BLEND_NOT_PREMULT; + else + node->node.GROUP0.B2R2_ACK |= + B2R2_ACK_MODE_BLEND_PREMULT; + + /* + * global_alpha register accepts 0..128 range, + * global_alpha in the request is 0..255, remap needed. + */ + if (req->user_req.flags & B2R2_BLT_FLAG_GLOBAL_ALPHA_BLEND) { + if (global_alpha == 255) + global_alpha = 128; + else + global_alpha >>= 1; + } else { + /* + * Use solid global_alpha + * if global alpha blending is not set. + */ + global_alpha = 128; + } + + node->node.GROUP0.B2R2_ACK |= + global_alpha << (B2R2_ACK_GALPHA_ROPID_SHIFT); + + /* Set background on SRC1 channel */ + node->node.GROUP3.B2R2_SBA = bg_buf->phys_addr; + node->node.GROUP3.B2R2_STY = + (B2R2_GENERIC_WORK_BUF_PITCH << + B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + /* Set foreground on SRC2 channel */ + node->node.GROUP4.B2R2_SBA = fg_buf->phys_addr; + node->node.GROUP4.B2R2_STY = + (B2R2_GENERIC_WORK_BUF_PITCH << + B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + /* Set target buffer */ + node->node.GROUP1.B2R2_TBA = bg_buf->phys_addr; + node->node.GROUP1.B2R2_TTY = + (B2R2_GENERIC_WORK_BUF_PITCH << + B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_1_FETCH_FROM_MEM | + B2R2_INS_SOURCE_2_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_SOURCE_1 | + B2R2_CIC_SOURCE_2; + } else { + /* + * No blending, foreground goes on SRC2. No global alpha. + * EMACSOC TODO: The blending stage should be skipped altogether + * if no blending is to be done. Probably could go directly from + * transform to writeback. + */ + node->node.GROUP0.B2R2_ACK |= B2R2_ACK_MODE_BYPASS_S2_S3; + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_2_FETCH_FROM_MEM; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_SOURCE_2; + + node->node.GROUP4.B2R2_SBA = fg_buf->phys_addr; + node->node.GROUP4.B2R2_STY = + (B2R2_GENERIC_WORK_BUF_PITCH << + B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + node->node.GROUP1.B2R2_TBA = bg_buf->phys_addr; + node->node.GROUP1.B2R2_TTY = + (B2R2_GENERIC_WORK_BUF_PITCH << + B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + } + + b2r2_log_info("%s DONE\n", __func__); +} + +static void setup_writeback_stage(const struct b2r2_blt_request *req, + struct b2r2_node *node, + struct b2r2_work_buf *in_buf) +{ + const struct b2r2_blt_img *dst_img = &(req->user_req.dst_img); + const enum b2r2_blt_fmt dst_fmt = dst_img->fmt; + const bool yuv_planar_dst = + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR; + + const bool yuv_semi_planar_dst = + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE; + + const u32 group4_b2r2_sty = + (B2R2_GENERIC_WORK_BUF_PITCH << B2R2_TY_BITMAP_PITCH_SHIFT) | + B2R2_GENERIC_WORK_BUF_FMT | + B2R2_TY_ALPHA_RANGE_255 | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM; + + u32 dst_dither = 0; + u32 dst_pitch = 0; + u32 endianness = 0; + + b2r2_log_info("%s ENTRY\n", __func__); + + if (dst_img->pitch == 0) { + /* Determine pitch based on format and width of the image. */ + dst_pitch = get_pitch(dst_img->fmt, dst_img->width); + } else + dst_pitch = dst_img->pitch; + + if ((req->user_req.flags & B2R2_BLT_FLAG_DITHER) != 0) + dst_dither = B2R2_TTY_RGB_ROUND_DITHER; + + /* Set target buffer(s) */ + if (yuv_planar_dst) { + /* + * three nodes required to write the output. + * Luma, blue chroma and red chroma. + */ + u32 fctl = 0; + u32 rsf = 0; + const u32 group0_b2r2_ins = + B2R2_INS_SOURCE_2_FETCH_FROM_MEM | + B2R2_INS_RECT_CLIP_ENABLED | + B2R2_INS_IVMX_ENABLED; + const u32 group0_b2r2_cic = + B2R2_CIC_SOURCE_2 | + B2R2_CIC_CLIP_WINDOW | + B2R2_CIC_IVMX; + + u32 cb_addr = 0; + u32 cr_addr = 0; + u32 chroma_pitch = 0; + bool swapped_chroma = + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR; + enum b2r2_native_fmt dst_native_fmt = + to_native_fmt(dst_img->fmt); + enum b2r2_ty alpha_range = get_alpha_range(dst_img->fmt); + + if (swapped_chroma) + cr_addr = req->dst_resolved.physical_address + + dst_pitch * dst_img->height; + else + cb_addr = req->dst_resolved.physical_address + + dst_pitch * dst_img->height; + + switch (dst_fmt) { + case B2R2_BLT_FMT_YUV420_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU420_PACKED_PLANAR: + chroma_pitch = dst_pitch >> 1; + if (swapped_chroma) + cb_addr = cr_addr + chroma_pitch * + (dst_img->height >> 1); + else + cr_addr = cb_addr + chroma_pitch * + (dst_img->height >> 1); + /* + * Chrominance is always half the luminance size + * so chrominance resizer is always active. + */ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | + B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (2 << 10) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= (2 << 10) << B2R2_RSF_VSRC_INC_SHIFT; + break; + case B2R2_BLT_FMT_YUV422_PACKED_PLANAR: + case B2R2_BLT_FMT_YVU422_PACKED_PLANAR: + chroma_pitch = dst_pitch >> 1; + if (swapped_chroma) + cb_addr = cr_addr + chroma_pitch * + dst_img->height; + else + cr_addr = cb_addr + chroma_pitch * + dst_img->height; + /* + * YUV422 or YVU422 + * Chrominance is always half the luminance size + * only in horizontal direction. + */ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (2 << 10) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= (1 << 10) << B2R2_RSF_VSRC_INC_SHIFT; + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + chroma_pitch = dst_pitch; + cr_addr = + cb_addr + chroma_pitch * dst_img->height; + /* + * No scaling required since + * chrominance is not subsampled. + */ + default: + break; + } + + /* Luma (Y-component) */ + node->node.GROUP1.B2R2_TBA = req->dst_resolved.physical_address; + node->node.GROUP1.B2R2_TTY = + (dst_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | alpha_range | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM | + dst_dither; + + node->node.GROUP15.B2R2_VMX0 = B2R2_VMX0_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = B2R2_VMX1_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = B2R2_VMX2_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = B2R2_VMX3_RGB_TO_YUV_601_VIDEO; + + /* bypass ALU, no blending here. Handled in its own stage. */ + node->node.GROUP0.B2R2_ACK = B2R2_ACK_MODE_BYPASS_S2_S3; + node->node.GROUP0.B2R2_INS = group0_b2r2_ins; + node->node.GROUP0.B2R2_CIC |= group0_b2r2_cic; + + /* Set source buffer on SRC2 channel */ + node->node.GROUP4.B2R2_SBA = in_buf->phys_addr; + node->node.GROUP4.B2R2_STY = group4_b2r2_sty; + + /* Blue chroma (U-component)*/ + node = node->next; + node->node.GROUP1.B2R2_TBA = cb_addr; + node->node.GROUP1.B2R2_TTY = + (chroma_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | alpha_range | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM | + dst_dither | + B2R2_TTY_CHROMA_NOT_LUMA; + + node->node.GROUP15.B2R2_VMX0 = B2R2_VMX0_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = B2R2_VMX1_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = B2R2_VMX2_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = B2R2_VMX3_RGB_TO_YUV_601_VIDEO; + + node->node.GROUP0.B2R2_ACK = B2R2_ACK_MODE_BYPASS_S2_S3; + node->node.GROUP0.B2R2_INS = group0_b2r2_ins; + node->node.GROUP0.B2R2_CIC |= group0_b2r2_cic; + if (dst_fmt != B2R2_BLT_FMT_YUV444_PACKED_PLANAR) { + node->node.GROUP0.B2R2_INS |= + B2R2_INS_RESCALE2D_ENABLED; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_FILTER_CONTROL | + B2R2_CIC_RESIZE_CHROMA; + /* Set the filter control and rescale registers */ + node->node.GROUP8.B2R2_FCTL = fctl; + node->node.GROUP9.B2R2_RSF = rsf; + node->node.GROUP9.B2R2_RZI = + B2R2_RZI_DEFAULT_HNB_REPEAT | + (2 << B2R2_RZI_VNB_REPEAT_SHIFT); + } + + node->node.GROUP4.B2R2_SBA = in_buf->phys_addr; + node->node.GROUP4.B2R2_STY = group4_b2r2_sty; + + + /* + * Red chroma (V-component) + * The flag B2R2_TTY_CB_NOT_CR actually works + * the other way around, i.e. as if it was + * CR_NOT_CB. + */ + node = node->next; + node->node.GROUP1.B2R2_TBA = cr_addr; + node->node.GROUP1.B2R2_TTY = + (chroma_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | alpha_range | + B2R2_TTY_CB_NOT_CR | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM | + dst_dither | + B2R2_TTY_CHROMA_NOT_LUMA; + + node->node.GROUP15.B2R2_VMX0 = B2R2_VMX0_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = B2R2_VMX1_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = B2R2_VMX2_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = B2R2_VMX3_RGB_TO_YUV_601_VIDEO; + + node->node.GROUP0.B2R2_ACK = B2R2_ACK_MODE_BYPASS_S2_S3; + node->node.GROUP0.B2R2_INS = group0_b2r2_ins; + node->node.GROUP0.B2R2_CIC |= group0_b2r2_cic; + if (dst_fmt != B2R2_BLT_FMT_YUV444_PACKED_PLANAR) { + node->node.GROUP0.B2R2_INS |= + B2R2_INS_RESCALE2D_ENABLED; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_FILTER_CONTROL | + B2R2_CIC_RESIZE_CHROMA; + /* Set the filter control and rescale registers */ + node->node.GROUP8.B2R2_FCTL = fctl; + node->node.GROUP9.B2R2_RSF = rsf; + node->node.GROUP9.B2R2_RZI = + B2R2_RZI_DEFAULT_HNB_REPEAT | + (2 << B2R2_RZI_VNB_REPEAT_SHIFT); + } + + node->node.GROUP4.B2R2_SBA = in_buf->phys_addr; + node->node.GROUP4.B2R2_STY = group4_b2r2_sty; + } else if (yuv_semi_planar_dst) { + /* + * two nodes required to write the output. + * One node for luma and one for interleaved chroma + * components. + */ + u32 fctl = 0; + u32 rsf = 0; + const u32 group0_b2r2_ins = + B2R2_INS_SOURCE_2_FETCH_FROM_MEM | + B2R2_INS_RECT_CLIP_ENABLED | + B2R2_INS_IVMX_ENABLED; + const u32 group0_b2r2_cic = + B2R2_CIC_SOURCE_2 | + B2R2_CIC_CLIP_WINDOW | + B2R2_CIC_IVMX; + + u32 chroma_addr = req->dst_resolved.physical_address + + dst_pitch * dst_img->height; + u32 chroma_pitch = dst_pitch; + enum b2r2_native_fmt dst_native_fmt = + to_native_fmt(dst_img->fmt); + enum b2r2_ty alpha_range = get_alpha_range(dst_img->fmt); + + if (dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + dst_fmt == + B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR) { + /* + * Chrominance is always half the luminance size + * so chrominance resizer is always active. + */ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER | + B2R2_FCTL_VF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (2 << 10) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= (2 << 10) << B2R2_RSF_VSRC_INC_SHIFT; + } else { + /* + * YUV422 + * Chrominance is always half the luminance size + * only in horizontal direction. + */ + fctl |= B2R2_FCTL_HF2D_MODE_ENABLE_RESIZER; + + rsf &= ~(0xffff << B2R2_RSF_HSRC_INC_SHIFT); + rsf |= (2 << 10) << B2R2_RSF_HSRC_INC_SHIFT; + rsf &= ~(0xffff << B2R2_RSF_VSRC_INC_SHIFT); + rsf |= (1 << 10) << B2R2_RSF_VSRC_INC_SHIFT; + } + + /* Luma (Y-component) */ + node->node.GROUP1.B2R2_TBA = req->dst_resolved.physical_address; + node->node.GROUP1.B2R2_TTY = + (dst_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | alpha_range | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM | + dst_dither; + + if (dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR) { + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_RGB_TO_YVU_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_RGB_TO_YVU_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_RGB_TO_YVU_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_RGB_TO_YVU_601_VIDEO; + } else { + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_RGB_TO_YUV_601_VIDEO; + } + + /* bypass ALU, no blending here. Handled in its own stage. */ + node->node.GROUP0.B2R2_ACK = B2R2_ACK_MODE_BYPASS_S2_S3; + node->node.GROUP0.B2R2_INS = group0_b2r2_ins; + node->node.GROUP0.B2R2_CIC |= group0_b2r2_cic; + + /* Set source buffer on SRC2 channel */ + node->node.GROUP4.B2R2_SBA = in_buf->phys_addr; + node->node.GROUP4.B2R2_STY = group4_b2r2_sty; + + /* Chroma (UV-components)*/ + node = node->next; + node->node.GROUP1.B2R2_TBA = chroma_addr; + node->node.GROUP1.B2R2_TTY = + (chroma_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + dst_native_fmt | alpha_range | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM | + dst_dither | + B2R2_TTY_CHROMA_NOT_LUMA; + + if (dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR) { + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_RGB_TO_YVU_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_RGB_TO_YVU_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_RGB_TO_YVU_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_RGB_TO_YVU_601_VIDEO; + } else { + node->node.GROUP15.B2R2_VMX0 = + B2R2_VMX0_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX1 = + B2R2_VMX1_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX2 = + B2R2_VMX2_RGB_TO_YUV_601_VIDEO; + node->node.GROUP15.B2R2_VMX3 = + B2R2_VMX3_RGB_TO_YUV_601_VIDEO; + } + + node->node.GROUP0.B2R2_ACK = B2R2_ACK_MODE_BYPASS_S2_S3; + node->node.GROUP0.B2R2_INS = + group0_b2r2_ins | B2R2_INS_RESCALE2D_ENABLED; + node->node.GROUP0.B2R2_CIC |= group0_b2r2_cic | + B2R2_CIC_FILTER_CONTROL | + B2R2_CIC_RESIZE_CHROMA; + + /* Set the filter control and rescale registers */ + node->node.GROUP8.B2R2_FCTL = fctl; + node->node.GROUP9.B2R2_RSF = rsf; + node->node.GROUP9.B2R2_RZI = + B2R2_RZI_DEFAULT_HNB_REPEAT | + (2 << B2R2_RZI_VNB_REPEAT_SHIFT); + + node->node.GROUP4.B2R2_SBA = in_buf->phys_addr; + node->node.GROUP4.B2R2_STY = group4_b2r2_sty; + } else { + /* single buffer target */ + + /* Set up OVMX */ + switch (dst_fmt) { + case B2R2_BLT_FMT_32_BIT_ABGR8888: + node->node.GROUP0.B2R2_INS |= B2R2_INS_OVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_OVMX; + node->node.GROUP16.B2R2_VMX0 = B2R2_VMX0_RGB_TO_BGR; + node->node.GROUP16.B2R2_VMX1 = B2R2_VMX1_RGB_TO_BGR; + node->node.GROUP16.B2R2_VMX2 = B2R2_VMX2_RGB_TO_BGR; + node->node.GROUP16.B2R2_VMX3 = B2R2_VMX3_RGB_TO_BGR; + break; + case B2R2_BLT_FMT_Y_CB_Y_CR: + node->node.GROUP0.B2R2_INS |= B2R2_INS_OVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_OVMX; + node->node.GROUP16.B2R2_VMX0 = + B2R2_VMX0_RGB_TO_YVU_601_VIDEO; + node->node.GROUP16.B2R2_VMX1 = + B2R2_VMX1_RGB_TO_YVU_601_VIDEO; + node->node.GROUP16.B2R2_VMX2 = + B2R2_VMX2_RGB_TO_YVU_601_VIDEO; + node->node.GROUP16.B2R2_VMX3 = + B2R2_VMX3_RGB_TO_YVU_601_VIDEO; + break; + case B2R2_BLT_FMT_24_BIT_YUV888: /* fall through */ + case B2R2_BLT_FMT_32_BIT_AYUV8888: /* fall through */ + case B2R2_BLT_FMT_24_BIT_VUY888: /* fall through */ + case B2R2_BLT_FMT_32_BIT_VUYA8888: + node->node.GROUP0.B2R2_INS |= B2R2_INS_OVMX_ENABLED; + node->node.GROUP0.B2R2_CIC |= B2R2_CIC_OVMX; + node->node.GROUP16.B2R2_VMX0 = + B2R2_VMX0_RGB_TO_BLT_YUV888_601_VIDEO; + node->node.GROUP16.B2R2_VMX1 = + B2R2_VMX1_RGB_TO_BLT_YUV888_601_VIDEO; + node->node.GROUP16.B2R2_VMX2 = + B2R2_VMX2_RGB_TO_BLT_YUV888_601_VIDEO; + node->node.GROUP16.B2R2_VMX3 = + B2R2_VMX3_RGB_TO_BLT_YUV888_601_VIDEO; + + /* + * Re-arrange color components from (A)YUV to VUY(A) + * when bytes are stored in memory. + */ + if (dst_fmt == B2R2_BLT_FMT_24_BIT_VUY888 || + dst_fmt == B2R2_BLT_FMT_32_BIT_VUYA8888) + endianness = B2R2_TY_ENDIAN_BIG_NOT_LITTLE; + break; + default: + break; + } + + node->node.GROUP1.B2R2_TBA = req->dst_resolved.physical_address; + node->node.GROUP1.B2R2_TTY = + (dst_pitch << B2R2_TY_BITMAP_PITCH_SHIFT) | + to_native_fmt(dst_img->fmt) | + get_alpha_range(dst_img->fmt) | + B2R2_TY_HSO_LEFT_TO_RIGHT | + B2R2_TY_VSO_TOP_TO_BOTTOM | + dst_dither | + endianness; + + node->node.GROUP0.B2R2_ACK = B2R2_ACK_MODE_BYPASS_S2_S3; + node->node.GROUP0.B2R2_INS |= + B2R2_INS_SOURCE_2_FETCH_FROM_MEM | + B2R2_INS_RECT_CLIP_ENABLED; + node->node.GROUP0.B2R2_CIC |= + B2R2_CIC_SOURCE_2 | B2R2_CIC_CLIP_WINDOW; + + if (req->user_req.flags & B2R2_BLT_FLAG_SOURCE_COLOR_KEY) { + 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(req->user_req.src_color, + req->user_req.src_img.fmt); + node->node.GROUP12.B2R2_KEY1 = key_color; + node->node.GROUP12.B2R2_KEY2 = key_color; + } + + /* Set source buffer on SRC2 channel */ + node->node.GROUP4.B2R2_SBA = in_buf->phys_addr; + node->node.GROUP4.B2R2_STY = group4_b2r2_sty; + } + /* + * Writeback is the last stage. Terminate the program chain + * to prevent out-of-control B2R2 execution. + */ + node->node.GROUP0.B2R2_NIP = 0; + + b2r2_log_info("%s DONE\n", __func__); +} + +/* + * Public functions + */ +void b2r2_generic_init() +{ + b2r2_filters_init(); +} + +void b2r2_generic_exit(void) +{ + b2r2_filters_exit(); +} + +int b2r2_generic_analyze(const struct b2r2_blt_request *req, + s32 *work_buf_width, + s32 *work_buf_height, + u32 *work_buf_count, + u32 *node_count) +{ + /* + * Need at least 4 nodes, read or fill input, read dst, blend + * and write back the result */ + u32 n_nodes = 4; + /* Need at least 2 bufs, 1 for blend output and 1 for input */ + u32 n_work_bufs = 2; + /* Horizontal and vertical scaling factors in 6.10 fixed point format */ + s32 h_scf = 1 << 10; + s32 v_scf = 1 << 10; + enum b2r2_blt_fmt dst_fmt = 0; + bool is_src_fill = false; + bool yuv_planar_dst; + bool yuv_semi_planar_dst; + + struct b2r2_blt_rect src_rect; + struct b2r2_blt_rect dst_rect; + + if (req == NULL || work_buf_width == NULL || work_buf_height == NULL || + work_buf_count == NULL || node_count == NULL) { + b2r2_log_warn("%s: Invalid in or out pointers:\n" + "req=0x%p\n" + "work_buf_width=0x%p work_buf_height=0x%p " + "work_buf_count=0x%p\n" + "node_count=0x%p.\n", + __func__, + req, + work_buf_width, work_buf_height, + work_buf_count, + node_count); + return -EINVAL; + } + + dst_fmt = req->user_req.dst_img.fmt; + + is_src_fill = (req->user_req.flags & + (B2R2_BLT_FLAG_SOURCE_FILL | + B2R2_BLT_FLAG_SOURCE_FILL_RAW)) != 0; + + yuv_planar_dst = + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR; + yuv_semi_planar_dst = + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE; + + *node_count = 0; + *work_buf_width = 0; + *work_buf_height = 0; + *work_buf_count = 0; + + if (req->user_req.transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) { + n_nodes++; + n_work_bufs++; + } + + if ((yuv_planar_dst || yuv_semi_planar_dst) && + (req->user_req.flags & B2R2_BLT_FLAG_SOURCE_FILL_RAW)) { + b2r2_log_warn("%s: Invalid combination: source_fill_raw" + " and multi-buffer destination.\n", + __func__); + return -EINVAL; + } + + if ((req->user_req.flags & B2R2_BLT_FLAG_SOURCE_COLOR_KEY) != 0 && + (req->user_req.flags & B2R2_BLT_FLAG_DEST_COLOR_KEY)) { + b2r2_log_warn("%s: Invalid combination: source and " + "destination color keying.\n", __func__); + return -EINVAL; + } + + if ((req->user_req.flags & + (B2R2_BLT_FLAG_SOURCE_FILL | + B2R2_BLT_FLAG_SOURCE_FILL_RAW)) && + (req->user_req.flags & + (B2R2_BLT_FLAG_SOURCE_COLOR_KEY | + B2R2_BLT_FLAG_DEST_COLOR_KEY))) { + b2r2_log_warn("%s: Invalid combination: " + "source_fill and color keying.\n", + __func__); + return -EINVAL; + } + + if ((req->user_req.flags & + (B2R2_BLT_FLAG_PER_PIXEL_ALPHA_BLEND | + B2R2_BLT_FLAG_GLOBAL_ALPHA_BLEND)) && + (req->user_req.flags & + (B2R2_BLT_FLAG_DEST_COLOR_KEY | + B2R2_BLT_FLAG_SOURCE_COLOR_KEY))) { + b2r2_log_warn("%s: Invalid combination: " + "blending and color keying.\n", + __func__); + return -EINVAL; + } + + if ((req->user_req.flags & B2R2_BLT_FLAG_SOURCE_MASK) && + (req->user_req.flags & + (B2R2_BLT_FLAG_DEST_COLOR_KEY | + B2R2_BLT_FLAG_SOURCE_COLOR_KEY))) { + b2r2_log_warn("%s: Invalid combination: source mask and " + "color keying.\n", + __func__); + return -EINVAL; + } + + if (req->user_req.flags & + (B2R2_BLT_FLAG_DEST_COLOR_KEY | + B2R2_BLT_FLAG_SOURCE_MASK)) { + b2r2_log_warn("%s: Unsupported: source mask, " + "destination color keying.\n", + __func__); + return -ENOSYS; + } + + if ((req->user_req.flags & B2R2_BLT_FLAG_SOURCE_MASK)) { + enum b2r2_blt_fmt src_fmt = req->user_req.src_img.fmt; + bool yuv_src = + src_fmt == B2R2_BLT_FMT_Y_CB_Y_CR || + src_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR || + src_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + src_fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR || + src_fmt == + B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE || + src_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE; + if (yuv_src || src_fmt == B2R2_BLT_FMT_1_BIT_A1 || + src_fmt == B2R2_BLT_FMT_8_BIT_A8) { + b2r2_log_warn("%s: Unsupported: source color keying " + "with YUV or pure alpha formats.\n", + __func__); + return -ENOSYS; + } + } + + /* Check for invalid dimensions that would hinder scale calculations */ + src_rect = req->user_req.src_rect; + dst_rect = req->user_req.dst_rect; + /* Check for invalid src_rect unless src_fill is enabled */ + if (!is_src_fill && (src_rect.x < 0 || src_rect.y < 0 || + src_rect.x + src_rect.width > req->user_req.src_img.width || + src_rect.y + src_rect.height > req->user_req.src_img.height)) { + b2r2_log_warn("%s: src_rect outside src_img:\n" + "src(x,y,w,h)=(%d, %d, %d, %d) " + "src_img(w,h)=(%d, %d).\n", + __func__, + src_rect.x, src_rect.y, src_rect.width, src_rect.height, + req->user_req.src_img.width, + req->user_req.src_img.height); + return -EINVAL; + } + + if (!is_src_fill && (src_rect.width <= 0 || src_rect.height <= 0)) { + b2r2_log_warn("%s: Invalid source dimensions:\n" + "src(w,h)=(%d, %d).\n", + __func__, + src_rect.width, src_rect.height); + return -EINVAL; + } + + if (dst_rect.width <= 0 || dst_rect.height <= 0) { + b2r2_log_warn("%s: Invalid dest dimensions:\n" + "dst(w,h)=(%d, %d).\n", + __func__, + dst_rect.width, dst_rect.height); + return -EINVAL; + } + + 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 invalid image params */ + if (!is_src_fill && validate_buf(&(req->user_req.src_img), + &(req->src_resolved))) + return -EINVAL; + + if (validate_buf(&(req->user_req.dst_img), &(req->dst_resolved))) + return -EINVAL; + + if (is_src_fill) { + /* + * Params correct for a source fill operation. + * No need for further checking. + */ + if (yuv_planar_dst) + n_nodes += 2; + else if (yuv_semi_planar_dst) + n_nodes++; + + *work_buf_width = B2R2_GENERIC_WORK_BUF_WIDTH; + *work_buf_height = B2R2_GENERIC_WORK_BUF_HEIGHT; + *work_buf_count = n_work_bufs; + *node_count = n_nodes; + b2r2_log_info("%s DONE buf_w=%d buf_h=%d buf_count=%d " + "node_count=%d\n", + __func__, + *work_buf_width, *work_buf_height, + *work_buf_count, *node_count); + return 0; + } + + /* + * Calculate scaling factors, all transform enum values + * that include rotation have the CCW_ROT_90 bit set. + */ + if (req->user_req.transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) { + h_scf = (src_rect.width << 10) / dst_rect.height; + v_scf = (src_rect.height << 10) / dst_rect.width; + } else { + h_scf = (src_rect.width << 10) / dst_rect.width; + v_scf = (src_rect.height << 10) / dst_rect.height; + } + + /* Check for degenerate/out_of_range scaling factors. */ + if (h_scf <= 0 || v_scf <= 0 || h_scf > 0x7C00 || v_scf > 0x7C00) { + b2r2_log_warn("%s: Dimensions result in degenerate or " + "out of range scaling:\n" + "src(w,h)=(%d, %d) " + "dst(w,h)=(%d,%d).\n" + "h_scf=0x%.8x, v_scf=0x%.8x\n", + __func__, + src_rect.width, src_rect.height, + dst_rect.width, dst_rect.height, + h_scf, v_scf); + return -EINVAL; + } + + if (yuv_planar_dst) + n_nodes += 2; + else if (yuv_semi_planar_dst) + n_nodes++; + + *work_buf_width = B2R2_GENERIC_WORK_BUF_WIDTH; + *work_buf_height = B2R2_GENERIC_WORK_BUF_HEIGHT; + *work_buf_count = n_work_bufs; + *node_count = n_nodes; + b2r2_log_info("%s DONE buf_w=%d buf_h=%d buf_count=%d node_count=%d\n", + __func__, + *work_buf_width, *work_buf_height, *work_buf_count, + *node_count); + return 0; +} + +/* + * + */ +int b2r2_generic_configure(const struct b2r2_blt_request *req, + struct b2r2_node *first, + struct b2r2_work_buf *tmp_bufs, + u32 buf_count) +{ + struct b2r2_node *node = NULL; + struct b2r2_work_buf *in_buf = NULL; + struct b2r2_work_buf *out_buf = NULL; + struct b2r2_work_buf *empty_buf = NULL; + +#ifdef B2R2_GENERIC_DEBUG + u32 needed_bufs = 0; + u32 needed_nodes = 0; + s32 work_buf_width = 0; + s32 work_buf_height = 0; + u32 n_nodes = 0; + int invalid_req = b2r2_generic_analyze(req, &work_buf_width, + &work_buf_height, &needed_bufs, + &needed_nodes); + if (invalid_req < 0) { + b2r2_log_warn("%s: Invalid request supplied, ec=%d\n", + __func__, invalid_req); + return -EINVAL; + } + + node = first; + + while (node != NULL) { + n_nodes++; + node = node->next; + } + if (n_nodes < needed_nodes) { + b2r2_log_warn("%s: Not enough nodes %d < %d.\n", + __func__, n_nodes, needed_nodes); + return -EINVAL; + } + + if (buf_count < needed_bufs) { + b2r2_log_warn("%s: Not enough buffers %d < %d.\n", + __func__, buf_count, needed_bufs); + return -EINVAL; + } + +#endif + + reset_nodes(first); + node = first; + empty_buf = tmp_bufs; + out_buf = empty_buf; + empty_buf++; + /* Prepare input tile. Color_fill or read from src */ + setup_input_stage(req, node, out_buf); + in_buf = out_buf; + out_buf = empty_buf; + empty_buf++; + node = node->next; + + if ((req->user_req.transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) != 0) { + setup_transform_stage(req, node, out_buf, in_buf); + node = node->next; + in_buf = out_buf; + out_buf = empty_buf++; + } + /* EMACSOC TODO: mask */ + /* + if (req->user_req.flags & B2R2_BLT_FLAG_SOURCE_MASK) { + setup_mask_stage(req, node, out_buf, in_buf); + node = node->next; + in_buf = out_buf; + out_buf = empty_buf++; + } + */ + /* Read the part of destination that will be updated */ + setup_dst_read_stage(req, node, out_buf); + node = node->next; + setup_blend_stage(req, node, out_buf, in_buf); + node = node->next; + in_buf = out_buf; + setup_writeback_stage(req, node, in_buf); + return 0; +} + +void b2r2_generic_set_areas(const struct b2r2_blt_request *req, + struct b2r2_node *first, + struct b2r2_blt_rect *dst_rect_area) +{ + /* + * Nodes come in the following order: <input stage>, [transform], + * [src_mask], <dst_read>, <blend>, <writeback> + */ + struct b2r2_node *node = first; + const struct b2r2_blt_rect *dst_rect = &(req->user_req.dst_rect); + const struct b2r2_blt_rect *src_rect = &(req->user_req.src_rect); + const enum b2r2_blt_fmt src_fmt = req->user_req.src_img.fmt; + bool yuv_multi_buffer_src = + src_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR || + src_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + src_fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR || + src_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE || + src_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE; + const enum b2r2_blt_fmt dst_fmt = req->user_req.dst_img.fmt; + const bool yuv_multi_buffer_dst = + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE; + s32 h_scf = 1 << 10; + s32 v_scf = 1 << 10; + s32 src_x = 0; + s32 src_y = 0; + s32 src_w = 0; + s32 src_h = 0; + u32 b2r2_rzi = 0; + s32 clip_top = 0; + s32 clip_left = 0; + s32 clip_bottom = req->user_req.dst_img.height - 1; + s32 clip_right = req->user_req.dst_img.width - 1; + /* Dst coords inside the dst_rect, not the buffer */ + s32 dst_x = dst_rect_area->x; + s32 dst_y = dst_rect_area->y; + + b2r2_log_info("%s ENTRY\n", __func__); + + if (req->user_req.transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) { + h_scf = (src_rect->width << 10) / dst_rect->height; + v_scf = (src_rect->height << 10) / dst_rect->width; + } else { + h_scf = (src_rect->width << 10) / dst_rect->width; + v_scf = (src_rect->height << 10) / dst_rect->height; + } + + if (req->user_req.transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) { + /* + * Normally the inverse transform for 90 degree rotation + * is given by: + * | 0 1| |x| | y| + * | | X | | = | | + * |-1 0| |y| |-x| + * but screen coordinates are flipped in y direction + * (compared to usual Cartesian coordinates), hence the offsets. + */ + src_x = (dst_rect->height - dst_y - dst_rect_area->height) * + h_scf; + src_y = dst_x * v_scf; + src_w = dst_rect_area->height * h_scf; + src_h = dst_rect_area->width * v_scf; + } else { + src_x = dst_x * h_scf; + src_y = dst_y * v_scf; + src_w = dst_rect_area->width * h_scf; + src_h = dst_rect_area->height * v_scf; + } + + b2r2_rzi |= ((src_x & 0x3ff) << B2R2_RZI_HSRC_INIT_SHIFT) | + ((src_y & 0x3ff) << B2R2_RZI_VSRC_INIT_SHIFT); + + /* + * src_w must contain all the pixels that contribute + * to a particular tile. + * ((x + 0x3ff) >> 10) is equivalent to ceiling(x), + * expressed in 6.10 fixed point format. + * Every destination tile, maps to a certain area in the source + * rectangle. The area in source will most likely not be a rectangle + * with exact integer dimensions whenever arbitrary scaling is involved. + * Consider the following example. + * Suppose, that width of the current destination tile maps + * to 1.7 pixels in source, starting at x == 5.4, as calculated + * using the scaling factor. + * This means that while the destination tile is written, + * the source should be read from x == 5.4 up to x == 5.4 + 1.7 == 7.1 + * Consequently, color from 3 pixels (x == 5, 6 and 7) + * needs to be read from source. + * The formula below the comment yields: + * ceil(0.4 + 1.7) == ceil(2.1) == 3 + * (src_x & 0x3ff) is the fractional part of src_x, + * which is expressed in 6.10 fixed point format. + * Thus, width of the source area should be 3 pixels wide, + * starting at x == 5. + * However, the reading should not start at x == 5.0 + * but a bit inside, namely x == 5.4 + * The B2R2_RZI register is used to instruct the HW to do so. + * It contains the fractional part that will be added to + * the first pixel coordinate, before incrementing the current source + * coordinate with the step specified in B2R2_RSF register. + * The same applies to scaling in vertical direction. + */ + src_w = ((src_x & 0x3ff) + src_w + 0x3ff) >> 10; + src_h = ((src_y & 0x3ff) + src_h + 0x3ff) >> 10; + + /* + * EMACSOC TODO: Remove this debug clamp, once tile size + * is taken into account in generic_analyze() + */ + if (src_w > 128) + src_w = 128; + + src_x >>= 10; + src_y >>= 10; + + if (req->user_req.transform & B2R2_BLT_TRANSFORM_FLIP_H) + src_x = src_rect->width - src_x - src_w; + + if (req->user_req.transform & B2R2_BLT_TRANSFORM_FLIP_V) + src_y = src_rect->height - src_y - src_h; + + /* + * Translate the src/dst_rect coordinates into true + * src/dst_buffer coordinates + */ + src_x += src_rect->x; + src_y += src_rect->y; + + dst_x += dst_rect->x; + dst_y += dst_rect->y; + + /* + * Clamp the src coords to buffer dimensions + * to prevent illegal reads. + */ + if (src_x < 0) + src_x = 0; + + if (src_y < 0) + src_y = 0; + + if ((src_x + src_w) > req->user_req.src_img.width) + src_w = req->user_req.src_img.width - src_x; + + if ((src_y + src_h) > req->user_req.src_img.height) + src_h = req->user_req.src_img.height - src_y; + + + /* The input node */ + if (yuv_multi_buffer_src) { + /* Luma on SRC3 */ + node->node.GROUP5.B2R2_SXY = + ((src_x & 0xffff) << B2R2_XY_X_SHIFT) | + ((src_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP5.B2R2_SSZ = + ((src_w & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((src_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + /* Clear and set only the SRC_INIT bits */ + node->node.GROUP10.B2R2_RZI &= + ~((0x3ff << B2R2_RZI_HSRC_INIT_SHIFT) | + (0x3ff << B2R2_RZI_VSRC_INIT_SHIFT)); + node->node.GROUP10.B2R2_RZI |= b2r2_rzi; + + node->node.GROUP9.B2R2_RZI &= + ~((0x3ff << B2R2_RZI_HSRC_INIT_SHIFT) | + (0x3ff << B2R2_RZI_VSRC_INIT_SHIFT)); + switch (src_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: + /* + * Chroma goes on SRC2 and potentially on SRC1. + * Chroma is half the size of luma. Must round up + * the chroma size to handle cases when luma size is not + * divisible by 2. + * E.g. luma width==7 requires chroma width==4. + * Chroma width==7/2==3 is only enough + * for luma width==6. + */ + node->node.GROUP4.B2R2_SXY = + (((src_x & 0xffff) >> 1) << B2R2_XY_X_SHIFT) | + (((src_y & 0xffff) >> 1) << B2R2_XY_Y_SHIFT); + node->node.GROUP4.B2R2_SSZ = + ((((src_w + 1) & 0xfff) >> 1) << + B2R2_SZ_WIDTH_SHIFT) | + ((((src_h + 1) & 0xfff) >> 1) << + B2R2_SZ_HEIGHT_SHIFT); + if (src_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + src_fmt == + B2R2_BLT_FMT_YVU420_PACKED_PLANAR) { + node->node.GROUP3.B2R2_SXY = + node->node.GROUP4.B2R2_SXY; + node->node.GROUP3.B2R2_SSZ = + node->node.GROUP4.B2R2_SSZ; + } + node->node.GROUP9.B2R2_RZI |= (b2r2_rzi >> 1) & + ((0x3ff << B2R2_RZI_HSRC_INIT_SHIFT) | + (0x3ff << B2R2_RZI_VSRC_INIT_SHIFT)); + break; + 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: + /* + * Chroma goes on SRC2 and potentially on SRC1. + * Now chroma is half the size of luma + * only in horizontal direction. + * Same rounding applies as for 420 formats above, + * except it is only done horizontally. + */ + node->node.GROUP4.B2R2_SXY = + (((src_x & 0xffff) >> 1) << B2R2_XY_X_SHIFT) | + ((src_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP4.B2R2_SSZ = + ((((src_w + 1) & 0xfff) >> 1) << + B2R2_SZ_WIDTH_SHIFT) | + ((src_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + if (src_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + src_fmt == + B2R2_BLT_FMT_YVU422_PACKED_PLANAR) { + node->node.GROUP3.B2R2_SXY = + node->node.GROUP4.B2R2_SXY; + node->node.GROUP3.B2R2_SSZ = + node->node.GROUP4.B2R2_SSZ; + } + node->node.GROUP9.B2R2_RZI |= + (((src_x & 0x3ff) >> 1) << + B2R2_RZI_HSRC_INIT_SHIFT) | + ((src_y & 0x3ff) << B2R2_RZI_VSRC_INIT_SHIFT); + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + /* + * Chroma goes on SRC2 and SRC1. + * It is the same size as luma. + */ + node->node.GROUP4.B2R2_SXY = + ((src_x & 0xffff) << B2R2_XY_X_SHIFT) | + ((src_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP4.B2R2_SSZ = + ((src_w & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((src_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + node->node.GROUP3.B2R2_SXY = node->node.GROUP4.B2R2_SXY; + node->node.GROUP3.B2R2_SSZ = node->node.GROUP4.B2R2_SSZ; + + /* Clear and set only the SRC_INIT bits */ + node->node.GROUP9.B2R2_RZI &= + ~((0x3ff << B2R2_RZI_HSRC_INIT_SHIFT) | + (0x3ff << B2R2_RZI_VSRC_INIT_SHIFT)); + node->node.GROUP9.B2R2_RZI |= b2r2_rzi; + break; + default: + break; + } + } else { + node->node.GROUP4.B2R2_SXY = + ((src_x & 0xffff) << B2R2_XY_X_SHIFT) | + ((src_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP4.B2R2_SSZ = + ((src_w & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((src_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + /* Clear and set only the SRC_INIT bits */ + node->node.GROUP9.B2R2_RZI &= + ~((0x3ff << B2R2_RZI_HSRC_INIT_SHIFT) | + (0x3ff << B2R2_RZI_VSRC_INIT_SHIFT)); + node->node.GROUP9.B2R2_RZI |= b2r2_rzi; + } + + node->node.GROUP1.B2R2_TXY = 0; + if (req->user_req.transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) { + /* + * dst_rect_area coordinates are specified + * after potential rotation. + * Input is read before rotation, hence the width and height + * need to be swapped. + * Horizontal and vertical flips are accomplished with + * suitable scanning order while writing + * to the temporary buffer. + */ + if (req->user_req.transform & B2R2_BLT_TRANSFORM_FLIP_H) { + node->node.GROUP1.B2R2_TXY |= + ((dst_rect_area->height - 1) & 0xffff) << + B2R2_XY_X_SHIFT; + } + + if (req->user_req.transform & B2R2_BLT_TRANSFORM_FLIP_V) { + node->node.GROUP1.B2R2_TXY |= + ((dst_rect_area->width - 1) & 0xffff) << + B2R2_XY_Y_SHIFT; + } + + node->node.GROUP1.B2R2_TSZ = + ((dst_rect_area->height & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->width & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + } else { + if (req->user_req.transform & B2R2_BLT_TRANSFORM_FLIP_H) { + node->node.GROUP1.B2R2_TXY |= + ((dst_rect_area->width - 1) & 0xffff) << + B2R2_XY_X_SHIFT; + } + + if (req->user_req.transform & B2R2_BLT_TRANSFORM_FLIP_V) { + node->node.GROUP1.B2R2_TXY |= + ((dst_rect_area->height - 1) & 0xffff) << + B2R2_XY_Y_SHIFT; + } + + node->node.GROUP1.B2R2_TSZ = + ((dst_rect_area->width & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + } + + if (req->user_req.flags & + (B2R2_BLT_FLAG_SOURCE_FILL | B2R2_BLT_FLAG_SOURCE_FILL_RAW)) { + /* + * Scan order for source fill should always be left-to-right + * and top-to-bottom. Fill the input tile from top left. + */ + node->node.GROUP1.B2R2_TXY = 0; + node->node.GROUP4.B2R2_SSZ = node->node.GROUP1.B2R2_TSZ; + } + + if (B2R2_GENERIC_DEBUG_AREAS && dst_rect_area->x == 0 && + dst_rect_area->y == 0) { + dump_nodes(node, false); + b2r2_log_debug("%s Input node done.\n", __func__); + } + + /* Transform */ + if ((req->user_req.transform & B2R2_BLT_TRANSFORM_CCW_ROT_90) != 0) { + /* + * Transform node operates on temporary buffers. + * Content always at top left, but scanning order + * has to be flipped during rotation. + * Width and height need to be considered as well, since + * a tile may not necessarily be filled completely. + * dst_rect_area dimensions are specified + * after potential rotation. + * Input is read before rotation, hence the width and height + * need to be swapped on src. + */ + node = node->next; + + node->node.GROUP4.B2R2_SXY = 0; + node->node.GROUP4.B2R2_SSZ = + ((dst_rect_area->height & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->width & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + /* Bottom line written first */ + node->node.GROUP1.B2R2_TXY = + ((dst_rect_area->height - 1) & 0xffff) << + B2R2_XY_Y_SHIFT; + + node->node.GROUP1.B2R2_TSZ = + ((dst_rect_area->width & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + + if (B2R2_GENERIC_DEBUG_AREAS && dst_rect_area->x == 0 && + dst_rect_area->y == 0) { + dump_nodes(node, false); + b2r2_log_debug("%s Tranform node done.\n", __func__); + } + } + + /* Source mask */ + if (req->user_req.flags & B2R2_BLT_FLAG_SOURCE_MASK) { + node = node->next; + /* + * Same coords for mask as for the input stage. + * Should the mask be transformed together with source? + * EMACSOC TODO: Apply mask before any + * transform/scaling is done. + * Otherwise it will be dst_ not src_mask. + */ + if (B2R2_GENERIC_DEBUG_AREAS && dst_rect_area->x == 0 && + dst_rect_area->y == 0) { + dump_nodes(node, false); + b2r2_log_debug("%s Source mask node done.\n", __func__); + } + } + + /* dst_read */ + if (yuv_multi_buffer_dst) { + s32 dst_w = dst_rect_area->width; + s32 dst_h = dst_rect_area->height; + bool yuv420_dst = + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE; + + bool yuv422_dst = + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_SEMI_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_SEMIPLANAR_MB_STE; + node = node->next; + /* Luma on SRC3 */ + node->node.GROUP5.B2R2_SXY = + ((dst_x & 0xffff) << B2R2_XY_X_SHIFT) | + ((dst_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP5.B2R2_SSZ = + ((dst_w & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((dst_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + if (yuv420_dst) { + /* + * Chroma goes on SRC2 and potentially on SRC1. + * Chroma is half the size of luma. Must round up + * the chroma size to handle cases when luma size is not + * divisible by 2. + * E.g. luma width==7 requires chroma width==4. + * Chroma width==7/2==3 is only enough + * for luma width==6. + */ + node->node.GROUP4.B2R2_SXY = + (((dst_x & 0xffff) >> 1) << B2R2_XY_X_SHIFT) | + (((dst_y & 0xffff) >> 1) << B2R2_XY_Y_SHIFT); + node->node.GROUP4.B2R2_SSZ = + ((((dst_w + 1) & 0xfff) >> 1) << + B2R2_SZ_WIDTH_SHIFT) | + ((((dst_h + 1) & 0xfff) >> 1) << + B2R2_SZ_HEIGHT_SHIFT); + + if (dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + dst_fmt == + B2R2_BLT_FMT_YVU420_PACKED_PLANAR) { + node->node.GROUP3.B2R2_SXY = + node->node.GROUP4.B2R2_SXY; + node->node.GROUP3.B2R2_SSZ = + node->node.GROUP4.B2R2_SSZ; + } + } else if (yuv422_dst) { + /* + * Chroma goes on SRC2 and potentially on SRC1. + * Now chroma is half the size of luma + * only in horizontal direction. + * Same rounding applies as for 420 formats above, + * except it is only done horizontally. + */ + node->node.GROUP4.B2R2_SXY = + (((dst_x & 0xffff) >> 1) << B2R2_XY_X_SHIFT) | + ((dst_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP4.B2R2_SSZ = + ((((dst_w + 1) & 0xfff) >> 1) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + if (dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + dst_fmt == + B2R2_BLT_FMT_YVU422_PACKED_PLANAR) { + node->node.GROUP3.B2R2_SXY = + node->node.GROUP4.B2R2_SXY; + node->node.GROUP3.B2R2_SSZ = + node->node.GROUP4.B2R2_SSZ; + } + } else if (dst_fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR) { + /* + * Chroma goes on SRC2 and SRC1. + * It is the same size as luma. + */ + node->node.GROUP4.B2R2_SXY = node->node.GROUP5.B2R2_SXY; + node->node.GROUP4.B2R2_SSZ = node->node.GROUP5.B2R2_SSZ; + node->node.GROUP3.B2R2_SXY = node->node.GROUP5.B2R2_SXY; + node->node.GROUP3.B2R2_SSZ = node->node.GROUP5.B2R2_SSZ; + } + + node->node.GROUP1.B2R2_TXY = 0; + node->node.GROUP1.B2R2_TSZ = + ((dst_w & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((dst_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + } else { + node = node->next; + node->node.GROUP4.B2R2_SXY = + ((dst_x & 0xffff) << B2R2_XY_X_SHIFT) | + ((dst_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP4.B2R2_SSZ = + ((dst_rect_area->width & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + node->node.GROUP1.B2R2_TXY = 0; + node->node.GROUP1.B2R2_TSZ = + ((dst_rect_area->width & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + } + + if (B2R2_GENERIC_DEBUG_AREAS && dst_rect_area->x == 0 && + dst_rect_area->y == 0) { + dump_nodes(node, false); + b2r2_log_debug("%s dst_read node done.\n", __func__); + } + + /* blend */ + node = node->next; + node->node.GROUP3.B2R2_SXY = 0; + node->node.GROUP3.B2R2_SSZ = + ((dst_rect_area->width & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + /* contents of the foreground temporary buffer always at top left */ + node->node.GROUP4.B2R2_SXY = 0; + node->node.GROUP4.B2R2_SSZ = + ((dst_rect_area->width & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + node->node.GROUP1.B2R2_TXY = 0; + node->node.GROUP1.B2R2_TSZ = + ((dst_rect_area->width & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + if (B2R2_GENERIC_DEBUG_AREAS && dst_rect_area->x == 0 && + dst_rect_area->y == 0) { + dump_nodes(node, false); + b2r2_log_debug("%s Blend node done.\n", __func__); + } + + /* writeback */ + node = node->next; + if ((req->user_req.flags & B2R2_BLT_FLAG_DESTINATION_CLIP) != 0) { + clip_left = req->user_req.dst_clip_rect.x; + clip_top = req->user_req.dst_clip_rect.y; + clip_right = clip_left + req->user_req.dst_clip_rect.width - 1; + clip_bottom = clip_top + req->user_req.dst_clip_rect.height - 1; + } + /* + * Clamp the dst clip rectangle to buffer dimensions to prevent + * illegal writes. An illegal clip rectangle, e.g. outside the + * buffer will be ignored, resulting in nothing being clipped. + */ + if (clip_left < 0 || req->user_req.dst_img.width <= clip_left) + clip_left = 0; + + if (clip_top < 0 || req->user_req.dst_img.height <= clip_top) + clip_top = 0; + + if (clip_right < 0 || req->user_req.dst_img.width <= clip_right) + clip_right = req->user_req.dst_img.width - 1; + + if (clip_bottom < 0 || req->user_req.dst_img.height <= clip_bottom) + clip_bottom = req->user_req.dst_img.height - 1; + + /* + * Only allow writing inside the clip rect. + * INTNL bit in B2R2_CWO should be zero. + */ + node->node.GROUP6.B2R2_CWO = + ((clip_top & 0x7fff) << B2R2_CWO_Y_SHIFT) | + ((clip_left & 0x7fff) << B2R2_CWO_X_SHIFT); + node->node.GROUP6.B2R2_CWS = + ((clip_bottom & 0x7fff) << B2R2_CWS_Y_SHIFT) | + ((clip_right & 0x7fff) << B2R2_CWS_X_SHIFT); + + if (yuv_multi_buffer_dst) { + const s32 dst_w = dst_rect_area->width; + const s32 dst_h = dst_rect_area->height; + int i = 0; + /* Number of nodes required to write chroma output */ + int n_nodes = 1; + if (dst_fmt == B2R2_BLT_FMT_YUV420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU420_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YVU422_PACKED_PLANAR || + dst_fmt == B2R2_BLT_FMT_YUV444_PACKED_PLANAR) + n_nodes = 2; + + node->node.GROUP4.B2R2_SXY = 0; + node->node.GROUP4.B2R2_SSZ = + ((dst_w & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((dst_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + /* Luma (Y-component) */ + node->node.GROUP1.B2R2_TXY = + ((dst_x & 0xffff) << B2R2_XY_X_SHIFT) | + ((dst_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP1.B2R2_TSZ = + ((dst_w & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((dst_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + node->node.GROUP6.B2R2_CWO = + ((clip_top & 0x7fff) << B2R2_CWO_Y_SHIFT) | + ((clip_left & 0x7fff) << B2R2_CWO_X_SHIFT); + node->node.GROUP6.B2R2_CWS = + ((clip_bottom & 0x7fff) << B2R2_CWS_Y_SHIFT) | + ((clip_right & 0x7fff) << B2R2_CWS_X_SHIFT); + + if (B2R2_GENERIC_DEBUG_AREAS && dst_rect_area->x == 0 && + dst_rect_area->y == 0) { + dump_nodes(node, false); + b2r2_log_debug("%s Writeback luma node done.\n", + __func__); + } + + node = node->next; + + /* + * Chroma components. 1 or 2 nodes + * for semi-planar or planar buffer respectively. + */ + for (i = 0; i < n_nodes && node != NULL; ++i) { + + node->node.GROUP4.B2R2_SXY = 0; + node->node.GROUP4.B2R2_SSZ = + ((dst_w & 0xfff) << B2R2_SZ_WIDTH_SHIFT) | + ((dst_h & 0xfff) << B2R2_SZ_HEIGHT_SHIFT); + + switch (dst_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: + /* + * Chroma is half the size of luma. + * Must round up the chroma size to handle + * cases when luma size is not divisible by 2. + * E.g. luma_width==7 requires chroma_width==4. + * Chroma_width==7/2==3 is only enough + * for luma_width==6. + */ + node->node.GROUP1.B2R2_TXY = + (((dst_x & 0xffff) >> 1) << + B2R2_XY_X_SHIFT) | + (((dst_y & 0xffff) >> 1) << + B2R2_XY_Y_SHIFT); + node->node.GROUP1.B2R2_TSZ = + ((((dst_w + 1) & 0xfff) >> 1) << + B2R2_SZ_WIDTH_SHIFT) | + ((((dst_h + 1) & 0xfff) >> 1) << + B2R2_SZ_HEIGHT_SHIFT); + break; + 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: + /* + * Now chroma is half the size of luma only + * in horizontal direction. + * Same rounding applies as + * for 420 formats above, except it is only + * done horizontally. + */ + node->node.GROUP1.B2R2_TXY = + (((dst_x & 0xffff) >> 1) << + B2R2_XY_X_SHIFT) | + ((dst_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP1.B2R2_TSZ = + ((((dst_w + 1) & 0xfff) >> 1) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_h & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + break; + case B2R2_BLT_FMT_YUV444_PACKED_PLANAR: + /* + * Chroma has the same resolution as luma. + */ + node->node.GROUP1.B2R2_TXY = + ((dst_x & 0xffff) << B2R2_XY_X_SHIFT) | + ((dst_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP1.B2R2_TSZ = + ((dst_w & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_h & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + break; + default: + break; + } + + node->node.GROUP6.B2R2_CWO = + ((clip_top & 0x7fff) << B2R2_CWO_Y_SHIFT) | + ((clip_left & 0x7fff) << B2R2_CWO_X_SHIFT); + node->node.GROUP6.B2R2_CWS = + ((clip_bottom & 0x7fff) << B2R2_CWS_Y_SHIFT) | + ((clip_right & 0x7fff) << B2R2_CWS_X_SHIFT); + + if (B2R2_GENERIC_DEBUG_AREAS && dst_rect_area->x == 0 && + dst_rect_area->y == 0) { + dump_nodes(node, false); + b2r2_log_debug("%s Writeback chroma node " + "%d of %d done.\n", + __func__, i + 1, n_nodes); + } + + node = node->next; + } + } else { + node->node.GROUP4.B2R2_SXY = 0; + node->node.GROUP4.B2R2_SSZ = + ((dst_rect_area->width & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + node->node.GROUP1.B2R2_TXY = + ((dst_x & 0xffff) << B2R2_XY_X_SHIFT) | + ((dst_y & 0xffff) << B2R2_XY_Y_SHIFT); + node->node.GROUP1.B2R2_TSZ = + ((dst_rect_area->width & 0xfff) << + B2R2_SZ_WIDTH_SHIFT) | + ((dst_rect_area->height & 0xfff) << + B2R2_SZ_HEIGHT_SHIFT); + + if (B2R2_GENERIC_DEBUG_AREAS && dst_rect_area->x == 0 && + dst_rect_area->y == 0) { + dump_nodes(node, false); + b2r2_log_debug("%s Writeback node done.\n", __func__); + } + } + + b2r2_log_info("%s DONE\n", __func__); +} |