| /* SPDX-License-Identifier: GPL-2.0-only | |
| * | |
| * Generic bit area copy and twister engine for packed pixel framebuffers | |
| * | |
| * Rewritten by: | |
| * Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org) | |
| * | |
| * Based on previous work of: | |
| * Copyright (C) 1999-2005 James Simmons <jsimmons@www.infradead.org> | |
| * Anton Vorontsov <avorontsov@ru.mvista.com> | |
| * Pavel Pisa <pisa@cmp.felk.cvut.cz> | |
| * Antonino Daplas <adaplas@hotpop.com> | |
| * Geert Uytterhoeven | |
| * and others | |
| * | |
| * NOTES: | |
| * | |
| * Handles native and foreign byte order on both endians, standard and | |
| * reverse pixel order in a byte (<8 BPP), word length of 32/64 bits, | |
| * bits per pixel from 1 to the word length. Handles line lengths at byte | |
| * granularity while maintaining aligned accesses. | |
| * | |
| * Optimized routines for word aligned copying and byte aligned copying | |
| * on reverse pixel framebuffers. | |
| */ | |
| #include "fb_draw.h" | |
| /* used when no reversing is necessary */ | |
| static inline unsigned long fb_no_reverse(unsigned long val, struct fb_reverse reverse) | |
| { | |
| return val; | |
| } | |
| /* modifies the masked area in a word */ | |
| static inline void fb_copy_offset_masked(unsigned long mask, int offset, | |
| const struct fb_address *dst, | |
| const struct fb_address *src) | |
| { | |
| fb_modify_offset(fb_read_offset(offset, src), mask, offset, dst); | |
| } | |
| /* copies the whole word */ | |
| static inline void fb_copy_offset(int offset, const struct fb_address *dst, | |
| const struct fb_address *src) | |
| { | |
| fb_write_offset(fb_read_offset(offset, src), offset, dst); | |
| } | |
| /* forward aligned copy */ | |
| static inline void fb_copy_aligned_fwd(const struct fb_address *dst, | |
| const struct fb_address *src, | |
| int end, struct fb_reverse reverse) | |
| { | |
| unsigned long first, last; | |
| first = fb_pixel_mask(dst->bits, reverse); | |
| last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), reverse); | |
| /* Same alignment for source and dest */ | |
| if (end <= BITS_PER_LONG) { | |
| /* Single word */ | |
| last = last ? (last & first) : first; | |
| /* Trailing bits */ | |
| if (last == ~0UL) | |
| fb_copy_offset(0, dst, src); | |
| else | |
| fb_copy_offset_masked(last, 0, dst, src); | |
| } else { | |
| /* Multiple destination words */ | |
| int offset = first != ~0UL; | |
| /* Leading bits */ | |
| if (offset) | |
| fb_copy_offset_masked(first, 0, dst, src); | |
| /* Main chunk */ | |
| end /= BITS_PER_LONG; | |
| while (offset + 4 <= end) { | |
| fb_copy_offset(offset + 0, dst, src); | |
| fb_copy_offset(offset + 1, dst, src); | |
| fb_copy_offset(offset + 2, dst, src); | |
| fb_copy_offset(offset + 3, dst, src); | |
| offset += 4; | |
| } | |
| while (offset < end) | |
| fb_copy_offset(offset++, dst, src); | |
| /* Trailing bits */ | |
| if (last) | |
| fb_copy_offset_masked(last, offset, dst, src); | |
| } | |
| } | |
| /* reverse aligned copy */ | |
| static inline void fb_copy_aligned_rev(const struct fb_address *dst, | |
| const struct fb_address *src, | |
| int end, struct fb_reverse reverse) | |
| { | |
| unsigned long first, last; | |
| first = fb_pixel_mask(dst->bits, reverse); | |
| last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), reverse); | |
| if (end <= BITS_PER_LONG) { | |
| /* Single word */ | |
| if (last) | |
| first &= last; | |
| if (first == ~0UL) | |
| fb_copy_offset(0, dst, src); | |
| else | |
| fb_copy_offset_masked(first, 0, dst, src); | |
| } else { | |
| /* Multiple destination words */ | |
| int offset = first != ~0UL; | |
| /* Trailing bits */ | |
| end /= BITS_PER_LONG; | |
| if (last) | |
| fb_copy_offset_masked(last, end, dst, src); | |
| /* Main chunk */ | |
| while (end >= offset + 4) { | |
| fb_copy_offset(end - 1, dst, src); | |
| fb_copy_offset(end - 2, dst, src); | |
| fb_copy_offset(end - 3, dst, src); | |
| fb_copy_offset(end - 4, dst, src); | |
| end -= 4; | |
| } | |
| while (end > offset) | |
| fb_copy_offset(--end, dst, src); | |
| /* Leading bits */ | |
| if (offset) | |
| fb_copy_offset_masked(first, 0, dst, src); | |
| } | |
| } | |
| static inline void fb_copy_aligned(struct fb_address *dst, struct fb_address *src, | |
| int width, u32 height, unsigned int bits_per_line, | |
| struct fb_reverse reverse, bool rev_copy) | |
| { | |
| if (rev_copy) | |
| while (height--) { | |
| fb_copy_aligned_rev(dst, src, width + dst->bits, reverse); | |
| fb_address_backward(dst, bits_per_line); | |
| fb_address_backward(src, bits_per_line); | |
| } | |
| else | |
| while (height--) { | |
| fb_copy_aligned_fwd(dst, src, width + dst->bits, reverse); | |
| fb_address_forward(dst, bits_per_line); | |
| fb_address_forward(src, bits_per_line); | |
| } | |
| } | |
| static __always_inline void fb_copy_fwd(const struct fb_address *dst, | |
| const struct fb_address *src, int width, | |
| unsigned long (*reorder)(unsigned long val, | |
| struct fb_reverse reverse), | |
| struct fb_reverse reverse) | |
| { | |
| unsigned long first, last; | |
| unsigned long d0, d1; | |
| int end = dst->bits + width; | |
| int shift, left, right; | |
| first = fb_pixel_mask(dst->bits, reverse); | |
| last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), reverse); | |
| shift = dst->bits - src->bits; | |
| right = shift & (BITS_PER_LONG - 1); | |
| left = -shift & (BITS_PER_LONG - 1); | |
| if (end <= BITS_PER_LONG) { | |
| /* Single destination word */ | |
| last = last ? (last & first) : first; | |
| if (shift < 0) { | |
| d0 = fb_left(reorder(fb_read_offset(-1, src), reverse), left); | |
| if (src->bits + width > BITS_PER_LONG) | |
| d0 |= fb_right(reorder(fb_read_offset(0, src), reverse), right); | |
| if (last == ~0UL) | |
| fb_write_offset(reorder(d0, reverse), 0, dst); | |
| else | |
| fb_modify_offset(reorder(d0, reverse), last, 0, dst); | |
| } else { | |
| d0 = fb_right(reorder(fb_read_offset(0, src), reverse), right); | |
| fb_modify_offset(reorder(d0, reverse), last, 0, dst); | |
| } | |
| } else { | |
| /* Multiple destination words */ | |
| int offset = first != ~0UL; | |
| /* Leading bits */ | |
| if (shift < 0) | |
| d0 = reorder(fb_read_offset(-1, src), reverse); | |
| else | |
| d0 = 0; | |
| /* 2 source words */ | |
| if (offset) { | |
| d1 = reorder(fb_read_offset(0, src), reverse); | |
| d0 = fb_left(d0, left) | fb_right(d1, right); | |
| fb_modify_offset(reorder(d0, reverse), first, 0, dst); | |
| d0 = d1; | |
| } | |
| /* Main chunk */ | |
| end /= BITS_PER_LONG; | |
| if (reorder == fb_no_reverse) | |
| while (offset + 4 <= end) { | |
| d1 = fb_read_offset(offset + 0, src); | |
| d0 = fb_left(d0, left) | fb_right(d1, right); | |
| fb_write_offset(d0, offset + 0, dst); | |
| d0 = d1; | |
| d1 = fb_read_offset(offset + 1, src); | |
| d0 = fb_left(d0, left) | fb_right(d1, right); | |
| fb_write_offset(d0, offset + 1, dst); | |
| d0 = d1; | |
| d1 = fb_read_offset(offset + 2, src); | |
| d0 = fb_left(d0, left) | fb_right(d1, right); | |
| fb_write_offset(d0, offset + 2, dst); | |
| d0 = d1; | |
| d1 = fb_read_offset(offset + 3, src); | |
| d0 = fb_left(d0, left) | fb_right(d1, right); | |
| fb_write_offset(d0, offset + 3, dst); | |
| d0 = d1; | |
| offset += 4; | |
| } | |
| while (offset < end) { | |
| d1 = reorder(fb_read_offset(offset, src), reverse); | |
| d0 = fb_left(d0, left) | fb_right(d1, right); | |
| fb_write_offset(reorder(d0, reverse), offset, dst); | |
| d0 = d1; | |
| offset++; | |
| } | |
| /* Trailing bits */ | |
| if (last) { | |
| d0 = fb_left(d0, left); | |
| if (src->bits + width | |
| > offset * BITS_PER_LONG + ((shift < 0) ? BITS_PER_LONG : 0)) | |
| d0 |= fb_right(reorder(fb_read_offset(offset, src), reverse), | |
| right); | |
| fb_modify_offset(reorder(d0, reverse), last, offset, dst); | |
| } | |
| } | |
| } | |
| static __always_inline void fb_copy_rev(const struct fb_address *dst, | |
| const struct fb_address *src, int end, | |
| unsigned long (*reorder)(unsigned long val, | |
| struct fb_reverse reverse), | |
| struct fb_reverse reverse) | |
| { | |
| unsigned long first, last; | |
| unsigned long d0, d1; | |
| int shift, left, right; | |
| first = fb_pixel_mask(dst->bits, reverse); | |
| last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), reverse); | |
| shift = dst->bits - src->bits; | |
| right = shift & (BITS_PER_LONG-1); | |
| left = -shift & (BITS_PER_LONG-1); | |
| if (end <= BITS_PER_LONG) { | |
| /* Single destination word */ | |
| if (last) | |
| first &= last; | |
| if (shift > 0) { | |
| d0 = fb_right(reorder(fb_read_offset(1, src), reverse), right); | |
| if (src->bits > left) | |
| d0 |= fb_left(reorder(fb_read_offset(0, src), reverse), left); | |
| fb_modify_offset(reorder(d0, reverse), first, 0, dst); | |
| } else { | |
| d0 = fb_left(reorder(fb_read_offset(0, src), reverse), left); | |
| if (src->bits + end - dst->bits > BITS_PER_LONG) | |
| d0 |= fb_right(reorder(fb_read_offset(1, src), reverse), right); | |
| if (first == ~0UL) | |
| fb_write_offset(reorder(d0, reverse), 0, dst); | |
| else | |
| fb_modify_offset(reorder(d0, reverse), first, 0, dst); | |
| } | |
| } else { | |
| /* Multiple destination words */ | |
| int offset = first != ~0UL; | |
| end /= BITS_PER_LONG; | |
| /* 2 source words */ | |
| if (fb_right(~0UL, right) & last) | |
| d0 = fb_right(reorder(fb_read_offset(end + 1, src), reverse), right); | |
| else | |
| d0 = 0; | |
| /* Trailing bits */ | |
| d1 = reorder(fb_read_offset(end, src), reverse); | |
| if (last) | |
| fb_modify_offset(reorder(fb_left(d1, left) | d0, reverse), | |
| last, end, dst); | |
| d0 = d1; | |
| /* Main chunk */ | |
| if (reorder == fb_no_reverse) | |
| while (end >= offset + 4) { | |
| d1 = fb_read_offset(end - 1, src); | |
| d0 = fb_left(d1, left) | fb_right(d0, right); | |
| fb_write_offset(d0, end - 1, dst); | |
| d0 = d1; | |
| d1 = fb_read_offset(end - 2, src); | |
| d0 = fb_left(d1, left) | fb_right(d0, right); | |
| fb_write_offset(d0, end - 2, dst); | |
| d0 = d1; | |
| d1 = fb_read_offset(end - 3, src); | |
| d0 = fb_left(d1, left) | fb_right(d0, right); | |
| fb_write_offset(d0, end - 3, dst); | |
| d0 = d1; | |
| d1 = fb_read_offset(end - 4, src); | |
| d0 = fb_left(d1, left) | fb_right(d0, right); | |
| fb_write_offset(d0, end - 4, dst); | |
| d0 = d1; | |
| end -= 4; | |
| } | |
| while (end > offset) { | |
| end--; | |
| d1 = reorder(fb_read_offset(end, src), reverse); | |
| d0 = fb_left(d1, left) | fb_right(d0, right); | |
| fb_write_offset(reorder(d0, reverse), end, dst); | |
| d0 = d1; | |
| } | |
| /* Leading bits */ | |
| if (offset) { | |
| d0 = fb_right(d0, right); | |
| if (src->bits > left) | |
| d0 |= fb_left(reorder(fb_read_offset(0, src), reverse), left); | |
| fb_modify_offset(reorder(d0, reverse), first, 0, dst); | |
| } | |
| } | |
| } | |
| static __always_inline void fb_copy(struct fb_address *dst, struct fb_address *src, | |
| int width, u32 height, unsigned int bits_per_line, | |
| unsigned long (*reorder)(unsigned long val, | |
| struct fb_reverse reverse), | |
| struct fb_reverse reverse, bool rev_copy) | |
| { | |
| if (rev_copy) | |
| while (height--) { | |
| int move = src->bits < dst->bits ? -1 : 0; | |
| fb_address_move_long(src, move); | |
| fb_copy_rev(dst, src, width + dst->bits, reorder, reverse); | |
| fb_address_backward(dst, bits_per_line); | |
| fb_address_backward(src, bits_per_line); | |
| fb_address_move_long(src, -move); | |
| } | |
| else | |
| while (height--) { | |
| int move = src->bits > dst->bits ? 1 : 0; | |
| fb_address_move_long(src, move); | |
| fb_copy_fwd(dst, src, width, reorder, reverse); | |
| fb_address_forward(dst, bits_per_line); | |
| fb_address_forward(src, bits_per_line); | |
| fb_address_move_long(src, -move); | |
| } | |
| } | |
| static inline void fb_copyarea(struct fb_info *p, const struct fb_copyarea *area) | |
| { | |
| int bpp = p->var.bits_per_pixel; | |
| u32 dy = area->dy; | |
| u32 sy = area->sy; | |
| u32 height = area->height; | |
| int width = area->width * bpp; | |
| unsigned int bits_per_line = BYTES_TO_BITS(p->fix.line_length); | |
| struct fb_reverse reverse = fb_reverse_init(p); | |
| struct fb_address dst = fb_address_init(p); | |
| struct fb_address src = dst; | |
| bool rev_copy = (dy > sy) || (dy == sy && area->dx > area->sx); | |
| if (rev_copy) { | |
| dy += height - 1; | |
| sy += height - 1; | |
| } | |
| fb_address_forward(&dst, dy*bits_per_line + area->dx*bpp); | |
| fb_address_forward(&src, sy*bits_per_line + area->sx*bpp); | |
| if (src.bits == dst.bits) | |
| fb_copy_aligned(&dst, &src, width, height, bits_per_line, reverse, rev_copy); | |
| else if (!reverse.byte && (!reverse.pixel || | |
| !((src.bits ^ dst.bits) & (BITS_PER_BYTE-1)))) { | |
| fb_copy(&dst, &src, width, height, bits_per_line, | |
| fb_no_reverse, reverse, rev_copy); | |
| } else | |
| fb_copy(&dst, &src, width, height, bits_per_line, | |
| fb_reverse_long, reverse, rev_copy); | |
| } |