2005-04-17 02:20:36 +04:00
/*
* Generic function for frame buffer with packed pixels of any depth .
*
* Copyright ( C ) 1999 - 2005 James Simmons < jsimmons @ www . infradead . org >
*
* 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
*
* 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 .
*
* The two functions or copying forward and backward could be split up like
* the ones for filling , i . e . in aligned and unaligned versions . This would
* help moving some redundant computations and branches out of the loop , too .
*/
# 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>
2007-05-08 11:39:08 +04:00
# include "fb_draw.h"
2005-04-17 02:20:36 +04:00
# if BITS_PER_LONG == 32
# define FB_WRITEL fb_writel
# define FB_READL fb_readl
# else
# define FB_WRITEL fb_writeq
# define FB_READL fb_readq
# endif
/*
* Generic bitwise copy algorithm
*/
static void
2008-04-28 13:14:49 +04:00
bitcpy ( struct fb_info * p , unsigned long __iomem * dst , int dst_idx ,
const unsigned long __iomem * src , int src_idx , int bits ,
unsigned n , u32 bswapmask )
2005-04-17 02:20:36 +04:00
{
unsigned long first , last ;
int const shift = dst_idx - src_idx ;
int left , right ;
2008-04-28 13:14:49 +04:00
first = fb_shifted_pixels_mask_long ( p , dst_idx , bswapmask ) ;
last = ~ fb_shifted_pixels_mask_long ( p , ( dst_idx + n ) % bits , bswapmask ) ;
2005-04-17 02:20:36 +04:00
if ( ! shift ) {
// Same alignment for source and dest
if ( dst_idx + n < = bits ) {
// Single word
if ( last )
first & = last ;
FB_WRITEL ( comp ( FB_READL ( src ) , FB_READL ( dst ) , first ) , dst ) ;
} else {
// Multiple destination words
// Leading bits
if ( first ! = ~ 0UL ) {
FB_WRITEL ( comp ( FB_READL ( src ) , FB_READL ( dst ) , first ) , dst ) ;
dst + + ;
src + + ;
n - = bits - dst_idx ;
}
// Main chunk
n / = bits ;
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 ( comp ( FB_READL ( src ) , FB_READL ( dst ) , last ) , dst ) ;
}
} else {
2007-10-16 12:29:55 +04:00
/* Different alignment for source and dest */
2005-04-17 02:20:36 +04:00
unsigned long d0 , d1 ;
int m ;
right = shift & ( bits - 1 ) ;
left = - shift & ( bits - 1 ) ;
2007-10-16 12:29:55 +04:00
bswapmask & = shift ;
2005-04-17 02:20:36 +04:00
if ( dst_idx + n < = bits ) {
// Single destination word
if ( last )
first & = last ;
2007-10-16 12:29:55 +04:00
d0 = FB_READL ( src ) ;
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
2005-04-17 02:20:36 +04:00
if ( shift > 0 ) {
// Single source word
2007-10-16 12:29:55 +04:00
d0 > > = right ;
2005-04-17 02:20:36 +04:00
} else if ( src_idx + n < = bits ) {
// Single source word
2007-10-16 12:29:55 +04:00
d0 < < = left ; ;
2005-04-17 02:20:36 +04:00
} else {
// 2 source words
2007-10-16 12:29:55 +04:00
d1 = FB_READL ( src + 1 ) ;
d1 = fb_rev_pixels_in_long ( d1 , bswapmask ) ;
d0 = d0 < < left | d1 > > right ;
2005-04-17 02:20:36 +04:00
}
2007-10-16 12:29:55 +04:00
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
FB_WRITEL ( comp ( d0 , FB_READL ( dst ) , first ) , dst ) ;
2005-04-17 02:20:36 +04:00
} else {
// Multiple destination words
/** We must always remember the last value read, because in case
SRC and DST overlap bitwise ( e . g . when moving just one pixel in
1 bpp ) , we always collect one full long for DST and that might
overlap with the current long from SRC . We store this value in
' d0 ' . */
d0 = FB_READL ( src + + ) ;
2007-10-16 12:29:55 +04:00
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
2005-04-17 02:20:36 +04:00
// Leading bits
if ( shift > 0 ) {
// Single source word
2007-10-16 12:29:55 +04:00
d1 = d0 ;
d0 > > = right ;
2005-04-17 02:20:36 +04:00
dst + + ;
n - = bits - dst_idx ;
} else {
// 2 source words
d1 = FB_READL ( src + + ) ;
2007-10-16 12:29:55 +04:00
d1 = fb_rev_pixels_in_long ( d1 , bswapmask ) ;
d0 = d0 < < left | d1 > > right ;
2005-04-17 02:20:36 +04:00
dst + + ;
n - = bits - dst_idx ;
}
2007-10-16 12:29:55 +04:00
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
FB_WRITEL ( comp ( d0 , FB_READL ( dst ) , first ) , dst ) ;
d0 = d1 ;
2005-04-17 02:20:36 +04:00
// Main chunk
m = n % bits ;
n / = bits ;
2007-10-16 12:29:55 +04:00
while ( ( n > = 4 ) & & ! bswapmask ) {
2005-04-17 02:20:36 +04:00
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 + + ) ;
2007-10-16 12:29:55 +04:00
d1 = fb_rev_pixels_in_long ( d1 , bswapmask ) ;
d0 = d0 < < left | d1 > > right ;
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
FB_WRITEL ( d0 , dst + + ) ;
2005-04-17 02:20:36 +04:00
d0 = d1 ;
}
// Trailing bits
if ( last ) {
if ( m < = right ) {
// Single source word
2007-10-16 12:29:55 +04:00
d0 < < = left ;
2005-04-17 02:20:36 +04:00
} else {
// 2 source words
d1 = FB_READL ( src ) ;
2007-10-16 12:29:55 +04:00
d1 = fb_rev_pixels_in_long ( d1 ,
bswapmask ) ;
d0 = d0 < < left | d1 > > right ;
2005-04-17 02:20:36 +04:00
}
2007-10-16 12:29:55 +04:00
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
FB_WRITEL ( comp ( d0 , FB_READL ( dst ) , last ) , dst ) ;
2005-04-17 02:20:36 +04:00
}
}
}
}
/*
* Generic bitwise copy algorithm , operating backward
*/
static void
2008-04-28 13:14:49 +04:00
bitcpy_rev ( struct fb_info * p , unsigned long __iomem * dst , int dst_idx ,
const unsigned long __iomem * src , int src_idx , int bits ,
unsigned n , u32 bswapmask )
2005-04-17 02:20:36 +04:00
{
unsigned long first , last ;
int shift ;
dst + = ( n - 1 ) / bits ;
src + = ( n - 1 ) / bits ;
if ( ( n - 1 ) % bits ) {
dst_idx + = ( n - 1 ) % bits ;
dst + = dst_idx > > ( ffs ( bits ) - 1 ) ;
dst_idx & = bits - 1 ;
src_idx + = ( n - 1 ) % bits ;
src + = src_idx > > ( ffs ( bits ) - 1 ) ;
src_idx & = bits - 1 ;
}
shift = dst_idx - src_idx ;
2008-04-28 13:14:49 +04:00
first = fb_shifted_pixels_mask_long ( p , bits - 1 - dst_idx , bswapmask ) ;
last = ~ fb_shifted_pixels_mask_long ( p , bits - 1 - ( ( dst_idx - n ) % bits ) ,
bswapmask ) ;
2005-04-17 02:20:36 +04:00
if ( ! shift ) {
// Same alignment for source and dest
if ( ( unsigned long ) dst_idx + 1 > = n ) {
// Single word
if ( last )
first & = last ;
FB_WRITEL ( comp ( FB_READL ( src ) , FB_READL ( dst ) , first ) , dst ) ;
} else {
// Multiple destination words
// Leading bits
if ( first ! = ~ 0UL ) {
FB_WRITEL ( comp ( FB_READL ( src ) , FB_READL ( dst ) , first ) , dst ) ;
dst - - ;
src - - ;
n - = dst_idx + 1 ;
}
// Main chunk
n / = bits ;
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 ( comp ( FB_READL ( src ) , FB_READL ( dst ) , last ) , dst ) ;
}
} else {
// Different alignment for source and dest
2007-10-16 12:29:55 +04:00
unsigned long d0 , d1 ;
int m ;
2005-04-17 02:20:36 +04:00
int const left = - shift & ( bits - 1 ) ;
int const right = shift & ( bits - 1 ) ;
2007-10-16 12:29:55 +04:00
bswapmask & = shift ;
2005-04-17 02:20:36 +04:00
if ( ( unsigned long ) dst_idx + 1 > = n ) {
// Single destination word
if ( last )
first & = last ;
2007-10-16 12:29:55 +04:00
d0 = FB_READL ( src ) ;
2005-04-17 02:20:36 +04:00
if ( shift < 0 ) {
// Single source word
2007-10-16 12:29:55 +04:00
d0 < < = left ;
2005-04-17 02:20:36 +04:00
} else if ( 1 + ( unsigned long ) src_idx > = n ) {
// Single source word
2007-10-16 12:29:55 +04:00
d0 > > = right ;
2005-04-17 02:20:36 +04:00
} else {
// 2 source words
2007-10-16 12:29:55 +04:00
d1 = FB_READL ( src - 1 ) ;
d1 = fb_rev_pixels_in_long ( d1 , bswapmask ) ;
d0 = d0 > > right | d1 < < left ;
2005-04-17 02:20:36 +04:00
}
2007-10-16 12:29:55 +04:00
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
FB_WRITEL ( comp ( d0 , FB_READL ( dst ) , first ) , dst ) ;
2005-04-17 02:20:36 +04:00
} else {
// Multiple destination words
/** We must always remember the last value read, because in case
SRC and DST overlap bitwise ( e . g . when moving just one pixel in
1 bpp ) , we always collect one full long for DST and that might
overlap with the current long from SRC . We store this value in
' d0 ' . */
d0 = FB_READL ( src - - ) ;
2007-10-16 12:29:55 +04:00
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
2005-04-17 02:20:36 +04:00
// Leading bits
if ( shift < 0 ) {
// Single source word
2007-10-16 12:29:55 +04:00
d1 = d0 ;
d0 < < = left ;
2005-04-17 02:20:36 +04:00
} else {
// 2 source words
d1 = FB_READL ( src - - ) ;
2007-10-16 12:29:55 +04:00
d1 = fb_rev_pixels_in_long ( d1 , bswapmask ) ;
d0 = d0 > > right | d1 < < left ;
2005-04-17 02:20:36 +04:00
}
2007-10-16 12:29:55 +04:00
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
FB_WRITEL ( comp ( d0 , FB_READL ( dst ) , first ) , dst ) ;
d0 = d1 ;
2005-04-17 02:20:36 +04:00
dst - - ;
n - = dst_idx + 1 ;
// Main chunk
m = n % bits ;
n / = bits ;
2007-10-16 12:29:55 +04:00
while ( ( n > = 4 ) & & ! bswapmask ) {
2005-04-17 02:20:36 +04:00
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 - - ) ;
2007-10-16 12:29:55 +04:00
d1 = fb_rev_pixels_in_long ( d1 , bswapmask ) ;
d0 = d0 > > right | d1 < < left ;
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
FB_WRITEL ( d0 , dst - - ) ;
2005-04-17 02:20:36 +04:00
d0 = d1 ;
}
// Trailing bits
if ( last ) {
if ( m < = left ) {
// Single source word
2007-10-16 12:29:55 +04:00
d0 > > = right ;
2005-04-17 02:20:36 +04:00
} else {
// 2 source words
d1 = FB_READL ( src ) ;
2007-10-16 12:29:55 +04:00
d1 = fb_rev_pixels_in_long ( d1 ,
bswapmask ) ;
d0 = d0 > > right | d1 < < left ;
2005-04-17 02:20:36 +04:00
}
2007-10-16 12:29:55 +04:00
d0 = fb_rev_pixels_in_long ( d0 , bswapmask ) ;
FB_WRITEL ( comp ( d0 , FB_READL ( dst ) , last ) , dst ) ;
2005-04-17 02:20:36 +04:00
}
}
}
}
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 ;
unsigned long const bits_per_line = p - > fix . line_length * 8u ;
unsigned long __iomem * dst = NULL , * src = NULL ;
int bits = BITS_PER_LONG , bytes = bits > > 3 ;
int dst_idx = 0 , src_idx = 0 , rev_copy = 0 ;
2007-10-16 12:29:21 +04:00
u32 bswapmask = fb_compute_bswapmask ( p ) ;
2005-04-17 02:20:36 +04:00
if ( p - > state ! = FBINFO_STATE_RUNNING )
return ;
/* if the beginning of the target area might overlap with the end of
the source area , be have to copy the area reverse . */
if ( ( dy = = sy & & dx > sx ) | | ( dy > sy ) ) {
dy + = height ;
sy + = height ;
rev_copy = 1 ;
}
// split the base of the framebuffer into a long-aligned address and the
// index of the first bit
dst = src = ( unsigned long __iomem * ) ( ( unsigned long ) p - > screen_base & ~ ( bytes - 1 ) ) ;
dst_idx = src_idx = 8 * ( ( unsigned long ) p - > screen_base & ( bytes - 1 ) ) ;
// add offset of source and target area
dst_idx + = dy * bits_per_line + dx * p - > var . bits_per_pixel ;
src_idx + = sy * bits_per_line + sx * p - > var . bits_per_pixel ;
if ( p - > fbops - > fb_sync )
p - > fbops - > fb_sync ( p ) ;
if ( rev_copy ) {
while ( height - - ) {
dst_idx - = bits_per_line ;
src_idx - = bits_per_line ;
dst + = dst_idx > > ( ffs ( bits ) - 1 ) ;
dst_idx & = ( bytes - 1 ) ;
src + = src_idx > > ( ffs ( bits ) - 1 ) ;
src_idx & = ( bytes - 1 ) ;
2008-04-28 13:14:49 +04:00
bitcpy_rev ( p , dst , dst_idx , src , src_idx , bits ,
2007-10-16 12:29:21 +04:00
width * p - > var . bits_per_pixel , bswapmask ) ;
2005-04-17 02:20:36 +04:00
}
} else {
while ( height - - ) {
dst + = dst_idx > > ( ffs ( bits ) - 1 ) ;
dst_idx & = ( bytes - 1 ) ;
src + = src_idx > > ( ffs ( bits ) - 1 ) ;
src_idx & = ( bytes - 1 ) ;
2008-04-28 13:14:49 +04:00
bitcpy ( p , dst , dst_idx , src , src_idx , bits ,
2007-10-16 12:29:21 +04:00
width * p - > var . bits_per_pixel , bswapmask ) ;
2005-04-17 02:20:36 +04:00
dst_idx + = bits_per_line ;
src_idx + = bits_per_line ;
}
}
}
EXPORT_SYMBOL ( cfb_copyarea ) ;
MODULE_AUTHOR ( " James Simmons <jsimmons@users.sf.net> " ) ;
MODULE_DESCRIPTION ( " Generic software accelerated copyarea " ) ;
MODULE_LICENSE ( " GPL " ) ;