// SPDX-License-Identifier: GPL-2.0 or MIT /* * Copyright (C) 2016 Noralf Trønnes * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include #include static unsigned int clip_offset(const struct drm_rect *clip, unsigned int pitch, unsigned int cpp) { return clip->y1 * pitch + clip->x1 * cpp; } /** * drm_fb_clip_offset - Returns the clipping rectangles byte-offset in a framebuffer * @pitch: Framebuffer line pitch in byte * @format: Framebuffer format * @clip: Clip rectangle * * Returns: * The byte offset of the clip rectangle's top-left corner within the framebuffer. */ unsigned int drm_fb_clip_offset(unsigned int pitch, const struct drm_format_info *format, const struct drm_rect *clip) { return clip_offset(clip, pitch, format->cpp[0]); } EXPORT_SYMBOL(drm_fb_clip_offset); /** * drm_fb_memcpy - Copy clip buffer * @dst: Destination buffer * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @vaddr: Source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * * This function does not apply clipping on dst, i.e. the destination * is at the top-left corner. */ void drm_fb_memcpy(void *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { unsigned int cpp = fb->format->cpp[0]; size_t len = (clip->x2 - clip->x1) * cpp; unsigned int y, lines = clip->y2 - clip->y1; if (!dst_pitch) dst_pitch = len; vaddr += clip_offset(clip, fb->pitches[0], cpp); for (y = 0; y < lines; y++) { memcpy(dst, vaddr, len); vaddr += fb->pitches[0]; dst += dst_pitch; } } EXPORT_SYMBOL(drm_fb_memcpy); /** * drm_fb_memcpy_toio - Copy clip buffer * @dst: Destination buffer (iomem) * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @vaddr: Source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * * This function does not apply clipping on dst, i.e. the destination * is at the top-left corner. */ void drm_fb_memcpy_toio(void __iomem *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { unsigned int cpp = fb->format->cpp[0]; size_t len = (clip->x2 - clip->x1) * cpp; unsigned int y, lines = clip->y2 - clip->y1; if (!dst_pitch) dst_pitch = len; vaddr += clip_offset(clip, fb->pitches[0], cpp); for (y = 0; y < lines; y++) { memcpy_toio(dst, vaddr, len); vaddr += fb->pitches[0]; dst += dst_pitch; } } EXPORT_SYMBOL(drm_fb_memcpy_toio); /** * drm_fb_swab - Swap bytes into clip buffer * @dst: Destination buffer * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @src: Source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * @cached: Source buffer is mapped cached (eg. not write-combined) * * If @cached is false a temporary buffer is used to cache one pixel line at a * time to speed up slow uncached reads. * * This function does not apply clipping on dst, i.e. the destination * is at the top-left corner. */ void drm_fb_swab(void *dst, unsigned int dst_pitch, const void *src, const struct drm_framebuffer *fb, const struct drm_rect *clip, bool cached) { u8 cpp = fb->format->cpp[0]; size_t len = drm_rect_width(clip) * cpp; const u16 *src16; const u32 *src32; u16 *dst16; u32 *dst32; unsigned int x, y; void *buf = NULL; if (WARN_ON_ONCE(cpp != 2 && cpp != 4)) return; if (!dst_pitch) dst_pitch = len; if (!cached) buf = kmalloc(len, GFP_KERNEL); src += clip_offset(clip, fb->pitches[0], cpp); for (y = clip->y1; y < clip->y2; y++) { if (buf) { memcpy(buf, src, len); src16 = buf; src32 = buf; } else { src16 = src; src32 = src; } dst16 = dst; dst32 = dst; for (x = clip->x1; x < clip->x2; x++) { if (cpp == 4) *dst32++ = swab32(*src32++); else *dst16++ = swab16(*src16++); } src += fb->pitches[0]; dst += dst_pitch; } kfree(buf); } EXPORT_SYMBOL(drm_fb_swab); static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, const __le32 *sbuf, unsigned int pixels) { unsigned int x; u32 pix; for (x = 0; x < pixels; x++) { pix = le32_to_cpu(sbuf[x]); dbuf[x] = ((pix & 0x00e00000) >> 16) | ((pix & 0x0000e000) >> 11) | ((pix & 0x000000c0) >> 6); } } /** * drm_fb_xrgb8888_to_rgb332 - Convert XRGB8888 to RGB332 clip buffer * @dst: RGB332 destination buffer * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @src: XRGB8888 source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * * Drivers can use this function for RGB332 devices that don't natively support XRGB8888. */ void drm_fb_xrgb8888_to_rgb332(void *dst, unsigned int dst_pitch, const void *src, const struct drm_framebuffer *fb, const struct drm_rect *clip) { size_t width = drm_rect_width(clip); size_t src_len = width * sizeof(u32); unsigned int y; void *sbuf; if (!dst_pitch) dst_pitch = width; /* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */ sbuf = kmalloc(src_len, GFP_KERNEL); if (!sbuf) return; src += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < drm_rect_height(clip); y++) { memcpy(sbuf, src, src_len); drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width); src += fb->pitches[0]; dst += dst_pitch; } kfree(sbuf); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332); static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, const u32 *sbuf, unsigned int pixels, bool swab) { unsigned int x; u16 val16; for (x = 0; x < pixels; x++) { val16 = ((sbuf[x] & 0x00F80000) >> 8) | ((sbuf[x] & 0x0000FC00) >> 5) | ((sbuf[x] & 0x000000F8) >> 3); if (swab) dbuf[x] = swab16(val16); else dbuf[x] = val16; } } /** * drm_fb_xrgb8888_to_rgb565 - Convert XRGB8888 to RGB565 clip buffer * @dst: RGB565 destination buffer * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @vaddr: XRGB8888 source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * @swab: Swap bytes * * Drivers can use this function for RGB565 devices that don't natively * support XRGB8888. */ void drm_fb_xrgb8888_to_rgb565(void *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip, bool swab) { size_t linepixels = clip->x2 - clip->x1; size_t src_len = linepixels * sizeof(u32); size_t dst_len = linepixels * sizeof(u16); unsigned y, lines = clip->y2 - clip->y1; void *sbuf; if (!dst_pitch) dst_pitch = dst_len; /* * The cma memory is write-combined so reads are uncached. * Speed up by fetching one line at a time. */ sbuf = kmalloc(src_len, GFP_KERNEL); if (!sbuf) return; vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < lines; y++) { memcpy(sbuf, vaddr, src_len); drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels, swab); vaddr += fb->pitches[0]; dst += dst_pitch; } kfree(sbuf); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565); /** * drm_fb_xrgb8888_to_rgb565_toio - Convert XRGB8888 to RGB565 clip buffer * @dst: RGB565 destination buffer (iomem) * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @vaddr: XRGB8888 source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * @swab: Swap bytes * * Drivers can use this function for RGB565 devices that don't natively * support XRGB8888. */ void drm_fb_xrgb8888_to_rgb565_toio(void __iomem *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip, bool swab) { size_t linepixels = clip->x2 - clip->x1; size_t dst_len = linepixels * sizeof(u16); unsigned y, lines = clip->y2 - clip->y1; void *dbuf; if (!dst_pitch) dst_pitch = dst_len; dbuf = kmalloc(dst_len, GFP_KERNEL); if (!dbuf) return; vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < lines; y++) { drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels, swab); memcpy_toio(dst, dbuf, dst_len); vaddr += fb->pitches[0]; dst += dst_pitch; } kfree(dbuf); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565_toio); static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, const u32 *sbuf, unsigned int pixels) { unsigned int x; for (x = 0; x < pixels; x++) { *dbuf++ = (sbuf[x] & 0x000000FF) >> 0; *dbuf++ = (sbuf[x] & 0x0000FF00) >> 8; *dbuf++ = (sbuf[x] & 0x00FF0000) >> 16; } } /** * drm_fb_xrgb8888_to_rgb888 - Convert XRGB8888 to RGB888 clip buffer * @dst: RGB888 destination buffer * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @src: XRGB8888 source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * * Drivers can use this function for RGB888 devices that don't natively * support XRGB8888. */ void drm_fb_xrgb8888_to_rgb888(void *dst, unsigned int dst_pitch, const void *src, const struct drm_framebuffer *fb, const struct drm_rect *clip) { size_t width = drm_rect_width(clip); size_t src_len = width * sizeof(u32); unsigned int y; void *sbuf; if (!dst_pitch) dst_pitch = width * 3; /* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */ sbuf = kmalloc(src_len, GFP_KERNEL); if (!sbuf) return; src += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < drm_rect_height(clip); y++) { memcpy(sbuf, src, src_len); drm_fb_xrgb8888_to_rgb888_line(dst, sbuf, width); src += fb->pitches[0]; dst += dst_pitch; } kfree(sbuf); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888); /** * drm_fb_xrgb8888_to_rgb888_toio - Convert XRGB8888 to RGB888 clip buffer * @dst: RGB565 destination buffer (iomem) * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @vaddr: XRGB8888 source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * * Drivers can use this function for RGB888 devices that don't natively * support XRGB8888. */ void drm_fb_xrgb8888_to_rgb888_toio(void __iomem *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { size_t linepixels = clip->x2 - clip->x1; size_t dst_len = linepixels * 3; unsigned y, lines = clip->y2 - clip->y1; void *dbuf; if (!dst_pitch) dst_pitch = dst_len; dbuf = kmalloc(dst_len, GFP_KERNEL); if (!dbuf) return; vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < lines; y++) { drm_fb_xrgb8888_to_rgb888_line(dbuf, vaddr, linepixels); memcpy_toio(dst, dbuf, dst_len); vaddr += fb->pitches[0]; dst += dst_pitch; } kfree(dbuf); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888_toio); static void drm_fb_xrgb8888_to_xrgb2101010_line(u32 *dbuf, const u32 *sbuf, unsigned int pixels) { unsigned int x; u32 val32; for (x = 0; x < pixels; x++) { val32 = ((sbuf[x] & 0x000000FF) << 2) | ((sbuf[x] & 0x0000FF00) << 4) | ((sbuf[x] & 0x00FF0000) << 6); *dbuf++ = val32 | ((val32 >> 8) & 0x00300C03); } } /** * drm_fb_xrgb8888_to_xrgb2101010_toio - Convert XRGB8888 to XRGB2101010 clip * buffer * @dst: XRGB2101010 destination buffer (iomem) * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @vaddr: XRGB8888 source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * * Drivers can use this function for XRGB2101010 devices that don't natively * support XRGB8888. */ void drm_fb_xrgb8888_to_xrgb2101010_toio(void __iomem *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { size_t linepixels = clip->x2 - clip->x1; size_t dst_len = linepixels * sizeof(u32); unsigned int y, lines = clip->y2 - clip->y1; void *dbuf; if (!dst_pitch) dst_pitch = dst_len; dbuf = kmalloc(dst_len, GFP_KERNEL); if (!dbuf) return; vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < lines; y++) { drm_fb_xrgb8888_to_xrgb2101010_line(dbuf, vaddr, linepixels); memcpy_toio(dst, dbuf, dst_len); vaddr += fb->pitches[0]; dst += dst_pitch; } kfree(dbuf); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_xrgb2101010_toio); static void drm_fb_xrgb8888_to_gray8_line(u8 *dst, const u32 *src, unsigned int pixels) { unsigned int x; for (x = 0; x < pixels; x++) { u8 r = (*src & 0x00ff0000) >> 16; u8 g = (*src & 0x0000ff00) >> 8; u8 b = *src & 0x000000ff; /* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */ *dst++ = (3 * r + 6 * g + b) / 10; src++; } } /** * drm_fb_xrgb8888_to_gray8 - Convert XRGB8888 to grayscale * @dst: 8-bit grayscale destination buffer * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @vaddr: XRGB8888 source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * * Drm doesn't have native monochrome or grayscale support. * Such drivers can announce the commonly supported XR24 format to userspace * and use this function to convert to the native format. * * Monochrome drivers will use the most significant bit, * where 1 means foreground color and 0 background color. * * ITU BT.601 is used for the RGB -> luma (brightness) conversion. */ void drm_fb_xrgb8888_to_gray8(void *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { unsigned int linepixels = clip->x2 - clip->x1; unsigned int len = linepixels * sizeof(u32); unsigned int y; void *buf; u8 *dst8; u32 *src32; if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888)) return; if (!dst_pitch) dst_pitch = drm_rect_width(clip); /* * The cma memory is write-combined so reads are uncached. * Speed up by fetching one line at a time. */ buf = kmalloc(len, GFP_KERNEL); if (!buf) return; vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = clip->y1; y < clip->y2; y++) { dst8 = dst; src32 = memcpy(buf, vaddr, len); drm_fb_xrgb8888_to_gray8_line(dst8, src32, linepixels); vaddr += fb->pitches[0]; dst += dst_pitch; } kfree(buf); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_gray8); /** * drm_fb_blit_toio - Copy parts of a framebuffer to display memory * @dst: The display memory to copy to * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @dst_format: FOURCC code of the display's color format * @vmap: The framebuffer memory to copy from * @fb: The framebuffer to copy from * @clip: Clip rectangle area to copy * * This function copies parts of a framebuffer to display memory. If the * formats of the display and the framebuffer mismatch, the blit function * will attempt to convert between them. * * Returns: * 0 on success, or * -EINVAL if the color-format conversion failed, or * a negative error code otherwise. */ int drm_fb_blit_toio(void __iomem *dst, unsigned int dst_pitch, uint32_t dst_format, const void *vmap, const struct drm_framebuffer *fb, const struct drm_rect *clip) { uint32_t fb_format = fb->format->format; /* treat alpha channel like filler bits */ if (fb_format == DRM_FORMAT_ARGB8888) fb_format = DRM_FORMAT_XRGB8888; if (dst_format == DRM_FORMAT_ARGB8888) dst_format = DRM_FORMAT_XRGB8888; if (fb_format == DRM_FORMAT_ARGB2101010) fb_format = DRM_FORMAT_XRGB2101010; if (dst_format == DRM_FORMAT_ARGB2101010) dst_format = DRM_FORMAT_XRGB2101010; if (dst_format == fb_format) { drm_fb_memcpy_toio(dst, dst_pitch, vmap, fb, clip); return 0; } else if (dst_format == DRM_FORMAT_RGB565) { if (fb_format == DRM_FORMAT_XRGB8888) { drm_fb_xrgb8888_to_rgb565_toio(dst, dst_pitch, vmap, fb, clip, false); return 0; } } else if (dst_format == DRM_FORMAT_RGB888) { if (fb_format == DRM_FORMAT_XRGB8888) { drm_fb_xrgb8888_to_rgb888_toio(dst, dst_pitch, vmap, fb, clip); return 0; } } else if (dst_format == DRM_FORMAT_XRGB2101010) { if (fb_format == DRM_FORMAT_XRGB8888) { drm_fb_xrgb8888_to_xrgb2101010_toio(dst, dst_pitch, vmap, fb, clip); return 0; } } return -EINVAL; } EXPORT_SYMBOL(drm_fb_blit_toio); static void drm_fb_gray8_to_mono_reversed_line(u8 *dst, const u8 *src, unsigned int pixels, unsigned int start_offset, unsigned int end_len) { unsigned int xb, i; for (xb = 0; xb < pixels; xb++) { unsigned int start = 0, end = 8; u8 byte = 0x00; if (xb == 0 && start_offset) start = start_offset; if (xb == pixels - 1 && end_len) end = end_len; for (i = start; i < end; i++) { unsigned int x = xb * 8 + i; byte >>= 1; if (src[x] >> 7) byte |= BIT(7); } *dst++ = byte; } } /** * drm_fb_xrgb8888_to_mono_reversed - Convert XRGB8888 to reversed monochrome * @dst: reversed monochrome destination buffer * @dst_pitch: Number of bytes between two consecutive scanlines within dst * @src: XRGB8888 source buffer * @fb: DRM framebuffer * @clip: Clip rectangle area to copy * * DRM doesn't have native monochrome support. * Such drivers can announce the commonly supported XR24 format to userspace * and use this function to convert to the native format. * * This function uses drm_fb_xrgb8888_to_gray8() to convert to grayscale and * then the result is converted from grayscale to reversed monohrome. */ void drm_fb_xrgb8888_to_mono_reversed(void *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { unsigned int linepixels = drm_rect_width(clip); unsigned int lines = clip->y2 - clip->y1; unsigned int cpp = fb->format->cpp[0]; unsigned int len_src32 = linepixels * cpp; struct drm_device *dev = fb->dev; unsigned int start_offset, end_len; unsigned int y; u8 *mono = dst, *gray8; u32 *src32; if (drm_WARN_ON(dev, fb->format->format != DRM_FORMAT_XRGB8888)) return; /* * The reversed mono destination buffer contains 1 bit per pixel * and destination scanlines have to be in multiple of 8 pixels. */ if (!dst_pitch) dst_pitch = DIV_ROUND_UP(linepixels, 8); drm_WARN_ONCE(dev, dst_pitch % 8 != 0, "dst_pitch is not a multiple of 8\n"); /* * The cma memory is write-combined so reads are uncached. * Speed up by fetching one line at a time. * * Also, format conversion from XR24 to reversed monochrome * are done line-by-line but are converted to 8-bit grayscale * as an intermediate step. * * Allocate a buffer to be used for both copying from the cma * memory and to store the intermediate grayscale line pixels. */ src32 = kmalloc(len_src32 + linepixels, GFP_KERNEL); if (!src32) return; gray8 = (u8 *)src32 + len_src32; /* * For damage handling, it is possible that only parts of the source * buffer is copied and this could lead to start and end pixels that * are not aligned to multiple of 8. * * Calculate if the start and end pixels are not aligned and set the * offsets for the reversed mono line conversion function to adjust. */ start_offset = clip->x1 % 8; end_len = clip->x2 % 8; vaddr += clip_offset(clip, fb->pitches[0], cpp); for (y = 0; y < lines; y++) { src32 = memcpy(src32, vaddr, len_src32); drm_fb_xrgb8888_to_gray8_line(gray8, src32, linepixels); drm_fb_gray8_to_mono_reversed_line(mono, gray8, dst_pitch, start_offset, end_len); vaddr += fb->pitches[0]; mono += dst_pitch; } kfree(src32); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_mono_reversed);