| // 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 <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| |
| #include <drm/drm_format_helper.h> |
| #include <drm/drm_framebuffer.h> |
| #include <drm/drm_fourcc.h> |
| #include <drm/drm_rect.h> |
| |
| static unsigned int clip_offset(struct drm_rect *clip, |
| unsigned int pitch, unsigned int cpp) |
| { |
| return clip->y1 * pitch + clip->x1 * cpp; |
| } |
| |
| /** |
| * drm_fb_memcpy - Copy clip buffer |
| * @dst: Destination buffer |
| * @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 a small buffer containing the clip rect only. |
| */ |
| void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb, |
| 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; |
| |
| vaddr += clip_offset(clip, fb->pitches[0], cpp); |
| for (y = 0; y < lines; y++) { |
| memcpy(dst, vaddr, len); |
| vaddr += fb->pitches[0]; |
| dst += len; |
| } |
| } |
| EXPORT_SYMBOL(drm_fb_memcpy); |
| |
| /** |
| * drm_fb_memcpy_dstclip - 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 applies clipping on dst, i.e. the destination is a |
| * full (iomem) framebuffer but only the clip rect content is copied over. |
| */ |
| void drm_fb_memcpy_dstclip(void __iomem *dst, unsigned int dst_pitch, |
| void *vaddr, struct drm_framebuffer *fb, |
| struct drm_rect *clip) |
| { |
| unsigned int cpp = fb->format->cpp[0]; |
| unsigned int offset = clip_offset(clip, dst_pitch, cpp); |
| size_t len = (clip->x2 - clip->x1) * cpp; |
| unsigned int y, lines = clip->y2 - clip->y1; |
| |
| vaddr += offset; |
| dst += offset; |
| for (y = 0; y < lines; y++) { |
| memcpy_toio(dst, vaddr, len); |
| vaddr += fb->pitches[0]; |
| dst += dst_pitch; |
| } |
| } |
| EXPORT_SYMBOL(drm_fb_memcpy_dstclip); |
| |
| /** |
| * drm_fb_swab - Swap bytes into clip buffer |
| * @dst: Destination buffer |
| * @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 a small buffer containing the clip rect only. |
| */ |
| void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb, |
| struct drm_rect *clip, bool cached) |
| { |
| u8 cpp = fb->format->cpp[0]; |
| size_t len = drm_rect_width(clip) * cpp; |
| u16 *src16, *dst16 = dst; |
| u32 *src32, *dst32 = dst; |
| unsigned int x, y; |
| void *buf = NULL; |
| |
| if (WARN_ON_ONCE(cpp != 2 && cpp != 4)) |
| return; |
| |
| 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; |
| } |
| |
| for (x = clip->x1; x < clip->x2; x++) { |
| if (cpp == 4) |
| *dst32++ = swab32(*src32++); |
| else |
| *dst16++ = swab16(*src16++); |
| } |
| |
| src += fb->pitches[0]; |
| } |
| |
| kfree(buf); |
| } |
| EXPORT_SYMBOL(drm_fb_swab); |
| |
| static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, __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 |
| * @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. |
| * |
| * This function does not apply clipping on dst, i.e. the destination is a small buffer |
| * containing the clip rect only. |
| */ |
| void drm_fb_xrgb8888_to_rgb332(void *dst, void *src, struct drm_framebuffer *fb, |
| struct drm_rect *clip) |
| { |
| size_t width = drm_rect_width(clip); |
| size_t src_len = width * sizeof(u32); |
| unsigned int y; |
| void *sbuf; |
| |
| /* 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 += width; |
| } |
| |
| kfree(sbuf); |
| } |
| EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332); |
| |
| static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, 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 |
| * @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. |
| * |
| * This function does not apply clipping on dst, i.e. the destination |
| * is a small buffer containing the clip rect only. |
| */ |
| void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr, |
| struct drm_framebuffer *fb, |
| 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; |
| |
| /* |
| * 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_len; |
| } |
| |
| kfree(sbuf); |
| } |
| EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565); |
| |
| /** |
| * drm_fb_xrgb8888_to_rgb565_dstclip - Convert XRGB8888 to RGB565 clip buffer |
| * @dst: RGB565 destination buffer (iomem) |
| * @dst_pitch: destination buffer pitch |
| * @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. |
| * |
| * This function applies clipping on dst, i.e. the destination is a |
| * full (iomem) framebuffer but only the clip rect content is copied over. |
| */ |
| void drm_fb_xrgb8888_to_rgb565_dstclip(void __iomem *dst, unsigned int dst_pitch, |
| void *vaddr, struct drm_framebuffer *fb, |
| 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; |
| |
| dbuf = kmalloc(dst_len, GFP_KERNEL); |
| if (!dbuf) |
| return; |
| |
| vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); |
| dst += clip_offset(clip, dst_pitch, sizeof(u16)); |
| 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_len; |
| } |
| |
| kfree(dbuf); |
| } |
| EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565_dstclip); |
| |
| static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, 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 |
| * @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. |
| * |
| * This function does not apply clipping on dst, i.e. the destination |
| * is a small buffer containing the clip rect only. |
| */ |
| void drm_fb_xrgb8888_to_rgb888(void *dst, void *src, struct drm_framebuffer *fb, |
| struct drm_rect *clip) |
| { |
| size_t width = drm_rect_width(clip); |
| size_t src_len = width * sizeof(u32); |
| unsigned int y; |
| void *sbuf; |
| |
| /* 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 += width * 3; |
| } |
| |
| kfree(sbuf); |
| } |
| EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888); |
| |
| /** |
| * drm_fb_xrgb8888_to_rgb888_dstclip - Convert XRGB8888 to RGB888 clip buffer |
| * @dst: RGB565 destination buffer (iomem) |
| * @dst_pitch: destination buffer pitch |
| * @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. |
| * |
| * This function applies clipping on dst, i.e. the destination is a |
| * full (iomem) framebuffer but only the clip rect content is copied over. |
| */ |
| void drm_fb_xrgb8888_to_rgb888_dstclip(void __iomem *dst, unsigned int dst_pitch, |
| void *vaddr, struct drm_framebuffer *fb, |
| 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; |
| |
| dbuf = kmalloc(dst_len, GFP_KERNEL); |
| if (!dbuf) |
| return; |
| |
| vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); |
| dst += clip_offset(clip, dst_pitch, sizeof(u16)); |
| 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_len; |
| } |
| |
| kfree(dbuf); |
| } |
| EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888_dstclip); |
| |
| /** |
| * drm_fb_xrgb8888_to_gray8 - Convert XRGB8888 to grayscale |
| * @dst: 8-bit grayscale destination buffer |
| * @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(u8 *dst, void *vaddr, struct drm_framebuffer *fb, |
| struct drm_rect *clip) |
| { |
| unsigned int len = (clip->x2 - clip->x1) * sizeof(u32); |
| unsigned int x, y; |
| void *buf; |
| u32 *src; |
| |
| if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888)) |
| return; |
| /* |
| * 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; |
| |
| for (y = clip->y1; y < clip->y2; y++) { |
| src = vaddr + (y * fb->pitches[0]); |
| src += clip->x1; |
| memcpy(buf, src, len); |
| src = buf; |
| for (x = clip->x1; x < clip->x2; 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++; |
| } |
| } |
| |
| kfree(buf); |
| } |
| EXPORT_SYMBOL(drm_fb_xrgb8888_to_gray8); |
| |
| /** |
| * drm_fb_blit_rect_dstclip - 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. |
| * |
| * Use drm_fb_blit_dstclip() to copy the full framebuffer. |
| * |
| * Returns: |
| * 0 on success, or |
| * -EINVAL if the color-format conversion failed, or |
| * a negative error code otherwise. |
| */ |
| int drm_fb_blit_rect_dstclip(void __iomem *dst, unsigned int dst_pitch, |
| uint32_t dst_format, void *vmap, |
| struct drm_framebuffer *fb, |
| 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 (dst_format == fb_format) { |
| drm_fb_memcpy_dstclip(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_dstclip(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_dstclip(dst, dst_pitch, |
| vmap, fb, clip); |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| EXPORT_SYMBOL(drm_fb_blit_rect_dstclip); |
| |
| /** |
| * drm_fb_blit_dstclip - Copy 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 |
| * |
| * This function copies a full framebuffer to display memory. If the formats |
| * of the display and the framebuffer mismatch, the copy function will |
| * attempt to convert between them. |
| * |
| * See drm_fb_blit_rect_dstclip() for more information. |
| * |
| * Returns: |
| * 0 on success, or a negative error code otherwise. |
| */ |
| int drm_fb_blit_dstclip(void __iomem *dst, unsigned int dst_pitch, |
| uint32_t dst_format, void *vmap, |
| struct drm_framebuffer *fb) |
| { |
| struct drm_rect fullscreen = { |
| .x1 = 0, |
| .x2 = fb->width, |
| .y1 = 0, |
| .y2 = fb->height, |
| }; |
| return drm_fb_blit_rect_dstclip(dst, dst_pitch, dst_format, vmap, fb, |
| &fullscreen); |
| } |
| EXPORT_SYMBOL(drm_fb_blit_dstclip); |