blob: b4b286a4cfb12e61d9b3821df21d0dcfdfb577bd [file] [log] [blame]
/*
* Generic function for frame buffer with packed pixels of any depth.
*
* Copyright (C) June 1999 James Simmons
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*
* NOTES:
*
* This is for cfb packed pixels. Iplan and such are incorporated in the
* drivers that need them.
*
* FIXME
* The code for 24 bit is horrible. It copies byte by byte size instead of
* longs like the other sizes. Needs to be optimized.
*
* Also need to add code to deal with cards endians that are different than
* the native cpu endians. I also need to deal with MSB position in the word.
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/slab.h>
#include <asm/types.h>
#include <asm/io.h>
#define LONG_MASK (BITS_PER_LONG - 1)
#if BITS_PER_LONG == 32
#define FB_WRITEL fb_writel
#define FB_READL fb_readl
#define SHIFT_PER_LONG 5
#define BYTES_PER_LONG 4
#else
#define FB_WRITEL fb_writeq
#define FB_READL fb_readq
#define SHIFT_PER_LONG 6
#define BYTES_PER_LONG 8
#endif
static void bitcpy(unsigned long __iomem *dst, int dst_idx,
const unsigned long __iomem *src, int src_idx,
unsigned long n)
{
unsigned long first, last;
int shift = dst_idx-src_idx, left, right;
unsigned long d0, d1;
int m;
if (!n)
return;
shift = dst_idx-src_idx;
first = ~0UL >> dst_idx;
last = ~(~0UL >> ((dst_idx+n) % BITS_PER_LONG));
if (!shift) {
// Same alignment for source and dest
if (dst_idx+n <= BITS_PER_LONG) {
// Single word
if (last)
first &= last;
FB_WRITEL((FB_READL(src) & first) | (FB_READL(dst) & ~first), dst);
} else {
// Multiple destination words
// Leading bits
if (first) {
FB_WRITEL((FB_READL(src) & first) |
(FB_READL(dst) & ~first), dst);
dst++;
src++;
n -= BITS_PER_LONG-dst_idx;
}
// Main chunk
n /= BITS_PER_LONG;
while (n >= 8) {
FB_WRITEL(FB_READL(src++), dst++);
FB_WRITEL(FB_READL(src++), dst++);
FB_WRITEL(FB_READL(src++), dst++);
FB_WRITEL(FB_READL(src++), dst++);
FB_WRITEL(FB_READL(src++), dst++);
FB_WRITEL(FB_READL(src++), dst++);
FB_WRITEL(FB_READL(src++), dst++);
FB_WRITEL(FB_READL(src++), dst++);
n -= 8;
}
while (n--)
FB_WRITEL(FB_READL(src++), dst++);
// Trailing bits
if (last)
FB_WRITEL((FB_READL(src) & last) | (FB_READL(dst) & ~last), dst);
}
} else {
// Different alignment for source and dest
right = shift & (BITS_PER_LONG-1);
left = -shift & (BITS_PER_LONG-1);
if (dst_idx+n <= BITS_PER_LONG) {
// Single destination word
if (last)
first &= last;
if (shift > 0) {
// Single source word
FB_WRITEL(((FB_READL(src) >> right) & first) |
(FB_READL(dst) & ~first), dst);
} else if (src_idx+n <= BITS_PER_LONG) {
// Single source word
FB_WRITEL(((FB_READL(src) << left) & first) |
(FB_READL(dst) & ~first), dst);
} else {
// 2 source words
d0 = FB_READL(src++);
d1 = FB_READL(src);
FB_WRITEL(((d0<<left | d1>>right) & first) |
(FB_READL(dst) & ~first), dst);
}
} else {
// Multiple destination words
d0 = FB_READL(src++);
// Leading bits
if (shift > 0) {
// Single source word
FB_WRITEL(((d0 >> right) & first) |
(FB_READL(dst) & ~first), dst);
dst++;
n -= BITS_PER_LONG-dst_idx;
} else {
// 2 source words
d1 = FB_READL(src++);
FB_WRITEL(((d0<<left | d1>>right) & first) |
(FB_READL(dst) & ~first), dst);
d0 = d1;
dst++;
n -= BITS_PER_LONG-dst_idx;
}
// Main chunk
m = n % BITS_PER_LONG;
n /= BITS_PER_LONG;
while (n >= 4) {
d1 = FB_READL(src++);
FB_WRITEL(d0 << left | d1 >> right, dst++);
d0 = d1;
d1 = FB_READL(src++);
FB_WRITEL(d0 << left | d1 >> right, dst++);
d0 = d1;
d1 = FB_READL(src++);
FB_WRITEL(d0 << left | d1 >> right, dst++);
d0 = d1;
d1 = FB_READL(src++);
FB_WRITEL(d0 << left | d1 >> right, dst++);
d0 = d1;
n -= 4;
}
while (n--) {
d1 = FB_READL(src++);
FB_WRITEL(d0 << left | d1 >> right, dst++);
d0 = d1;
}
// Trailing bits
if (last) {
if (m <= right) {
// Single source word
FB_WRITEL(((d0 << left) & last) |
(FB_READL(dst) & ~last),
dst);
} else {
// 2 source words
d1 = FB_READL(src);
FB_WRITEL(((d0<<left | d1>>right) &
last) | (FB_READL(dst) &
~last), dst);
}
}
}
}
}
static void bitcpy_rev(unsigned long __iomem *dst, int dst_idx,
const unsigned long __iomem *src, int src_idx, unsigned long n)
{
unsigned long first, last;
int shift = dst_idx-src_idx, left, right;
unsigned long d0, d1;
int m;
if (!n)
return;
dst += (n-1)/BITS_PER_LONG;
src += (n-1)/BITS_PER_LONG;
if ((n-1) % BITS_PER_LONG) {
dst_idx += (n-1) % BITS_PER_LONG;
dst += dst_idx >> SHIFT_PER_LONG;
dst_idx &= BITS_PER_LONG-1;
src_idx += (n-1) % BITS_PER_LONG;
src += src_idx >> SHIFT_PER_LONG;
src_idx &= BITS_PER_LONG-1;
}
shift = dst_idx-src_idx;
first = ~0UL << (BITS_PER_LONG-1-dst_idx);
last = ~(~0UL << (BITS_PER_LONG-1-((dst_idx-n) % BITS_PER_LONG)));
if (!shift) {
// Same alignment for source and dest
if ((unsigned long)dst_idx+1 >= n) {
// Single word
if (last)
first &= last;
FB_WRITEL((FB_READL(src) & first) | (FB_READL(dst) & ~first), dst);
} else {
// Multiple destination words
// Leading bits
if (first) {
FB_WRITEL((FB_READL(src) & first) | (FB_READL(dst) & ~first), dst);
dst--;
src--;
n -= dst_idx+1;
}
// Main chunk
n /= BITS_PER_LONG;
while (n >= 8) {
FB_WRITEL(FB_READL(src--), dst--);
FB_WRITEL(FB_READL(src--), dst--);
FB_WRITEL(FB_READL(src--), dst--);
FB_WRITEL(FB_READL(src--), dst--);
FB_WRITEL(FB_READL(src--), dst--);
FB_WRITEL(FB_READL(src--), dst--);
FB_WRITEL(FB_READL(src--), dst--);
FB_WRITEL(FB_READL(src--), dst--);
n -= 8;
}
while (n--)
FB_WRITEL(FB_READL(src--), dst--);
// Trailing bits
if (last)
FB_WRITEL((FB_READL(src) & last) | (FB_READL(dst) & ~last), dst);
}
} else {
// Different alignment for source and dest
right = shift & (BITS_PER_LONG-1);
left = -shift & (BITS_PER_LONG-1);
if ((unsigned long)dst_idx+1 >= n) {
// Single destination word
if (last)
first &= last;
if (shift < 0) {
// Single source word
FB_WRITEL((FB_READL(src) << left & first) |
(FB_READL(dst) & ~first), dst);
} else if (1+(unsigned long)src_idx >= n) {
// Single source word
FB_WRITEL(((FB_READL(src) >> right) & first) |
(FB_READL(dst) & ~first), dst);
} else {
// 2 source words
d0 = FB_READL(src--);
d1 = FB_READL(src);
FB_WRITEL(((d0>>right | d1<<left) & first) |
(FB_READL(dst) & ~first), dst);
}
} else {
// Multiple destination words
d0 = FB_READL(src--);
// Leading bits
if (shift < 0) {
// Single source word
FB_WRITEL(((d0 << left) & first) |
(FB_READL(dst) & ~first), dst);
dst--;
n -= dst_idx+1;
} else {
// 2 source words
d1 = FB_READL(src--);
FB_WRITEL(((d0>>right | d1<<left) & first) |
(FB_READL(dst) & ~first), dst);
d0 = d1;
dst--;
n -= dst_idx+1;
}
// Main chunk
m = n % BITS_PER_LONG;
n /= BITS_PER_LONG;
while (n >= 4) {
d1 = FB_READL(src--);
FB_WRITEL(d0 >> right | d1 << left, dst--);
d0 = d1;
d1 = FB_READL(src--);
FB_WRITEL(d0 >> right | d1 << left, dst--);
d0 = d1;
d1 = FB_READL(src--);
FB_WRITEL(d0 >> right | d1 << left, dst--);
d0 = d1;
d1 = FB_READL(src--);
FB_WRITEL(d0 >> right | d1 << left, dst--);
d0 = d1;
n -= 4;
}
while (n--) {
d1 = FB_READL(src--);
FB_WRITEL(d0 >> right | d1 << left, dst--);
d0 = d1;
}
// Trailing bits
if (last) {
if (m <= left) {
// Single source word
FB_WRITEL(((d0 >> right) & last) |
(FB_READL(dst) & ~last),
dst);
} else {
// 2 source words
d1 = FB_READL(src);
FB_WRITEL(((d0>>right | d1<<left) &
last) | (FB_READL(dst) &
~last), dst);
}
}
}
}
}
void cfb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
{
u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy;
u32 height = area->height, width = area->width;
int x2, y2, old_dx, old_dy, vxres, vyres;
unsigned long next_line = p->fix.line_length;
int dst_idx = 0, src_idx = 0, rev_copy = 0;
unsigned long __iomem *dst = NULL, *src = NULL;
if (p->state != FBINFO_STATE_RUNNING)
return;
/* We want rotation but lack hardware to do it for us. */
if (!p->fbops->fb_rotate && p->var.rotate) {
}
vxres = p->var.xres_virtual;
vyres = p->var.yres_virtual;
if (area->dx > vxres || area->sx > vxres ||
area->dy > vyres || area->sy > vyres)
return;
/* clip the destination */
old_dx = area->dx;
old_dy = area->dy;
/*
* We could use hardware clipping but on many cards you get around
* hardware clipping by writing to framebuffer directly.
*/
x2 = area->dx + area->width;
y2 = area->dy + area->height;
dx = area->dx > 0 ? area->dx : 0;
dy = area->dy > 0 ? area->dy : 0;
x2 = x2 < vxres ? x2 : vxres;
y2 = y2 < vyres ? y2 : vyres;
width = x2 - dx;
height = y2 - dy;
/* update sx1,sy1 */
sx += (dx - old_dx);
sy += (dy - old_dy);
/* the source must be completely inside the virtual screen */
if (sx < 0 || sy < 0 ||
(sx + width) > vxres ||
(sy + height) > vyres)
return;
if ((dy == sy && dx > sx) ||
(dy > sy)) {
dy += height;
sy += height;
rev_copy = 1;
}
dst = src = (unsigned long __iomem *)((unsigned long)p->screen_base &
~(BYTES_PER_LONG-1));
dst_idx = src_idx = (unsigned long)p->screen_base & (BYTES_PER_LONG-1);
dst_idx += dy*next_line*8 + dx*p->var.bits_per_pixel;
src_idx += sy*next_line*8 + sx*p->var.bits_per_pixel;
if (p->fbops->fb_sync)
p->fbops->fb_sync(p);
if (rev_copy) {
while (height--) {
dst_idx -= next_line*8;
src_idx -= next_line*8;
dst += dst_idx >> SHIFT_PER_LONG;
dst_idx &= (BYTES_PER_LONG-1);
src += src_idx >> SHIFT_PER_LONG;
src_idx &= (BYTES_PER_LONG-1);
bitcpy_rev(dst, dst_idx, src, src_idx,
width*p->var.bits_per_pixel);
}
} else {
while (height--) {
dst += dst_idx >> SHIFT_PER_LONG;
dst_idx &= (BYTES_PER_LONG-1);
src += src_idx >> SHIFT_PER_LONG;
src_idx &= (BYTES_PER_LONG-1);
bitcpy(dst, dst_idx, src, src_idx,
width*p->var.bits_per_pixel);
dst_idx += next_line*8;
src_idx += next_line*8;
}
}
}
EXPORT_SYMBOL(cfb_copyarea);
MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>");
MODULE_DESCRIPTION("Generic software accelerated copyarea");
MODULE_LICENSE("GPL");