/*
 *  linux/drivers/video/fbcon-mac1.c
 *
 *    Optimized low-level frame buffer routines for the 6x11 console font
 *    on macintosh computers with internal monochrome video facilities.
 *
 *    Copyright Darik Horn <dajhorn at vanadac dot com>.
 *
 *    This file is subject to the terms and conditions of the GNU General Public
 *    License.  See the file named COPYING in the main directory of the Linux
 *    distribution package for the full text of this license.
 *
 *
 *  Usage:
 *
 *    1.  Patch this driver into your kernel source tree and recompile.
 *
 *    2.  From inside Mac OS click:
 *          Penguin Boot Loader -> File -> Settings -> Options -> Commmand Line
 *
 * 	3.  Add "video=inverse,font:ProFont6x11" to the command line without quotes.
 *
 *
 *  About the Macintosh frame buffer:
 *
 *    The internal monochrome frame buffer ("MFB") that is usually found in
 *    classic compact macintosh computers has a fixed resolution of 512x342
 *    pixels.
 *
 *    These computers can show 64 columns and 42 rows with the standard VGA 8x8
 *    font. However, most terminal software expects at least 80 columns, so it
 *    is much preferable to use the 6x11 macintosh font that can render 85
 *    columns and 31 rows at 512x342.
 *
 *    Pixels are packed into bytes, so each line of 512 pixels is 64 bytes wide
 *    in the frame buffer. A typical 512x342 frame buffer will be given by the
 *    kernel at p->screen_base and subsequently extend for 19,968 bytes. 
 *
 *    The most significant bit of the ( p->screen_base  +0 )th byte is the
 *    top-left pixel on the screen. The least significant bit of the
 *    ( p->screen_base +63 )th byte is the top-right pixel on the screen.
 *
 *    The basic problem with using the 6x11 font is that character cells on the
 *    display are not byte aligned.  When a character cell that crosses bytes
 *    is updated, the entire word must be read so that the contents of adjacent
 *    character cells may be preserved.
 *
 *    The usual default macintosh hardware palette has black letters on a white
 *    background.  This means that 0-bits in the frame buffer are white pixels
 *    on the screen, and that 1-bits in the frame buffer are black pixels on
 *    the screen.
 *
 *    This driver was developed on an Apple Macintosh SE/30.
 *
 */

#include <linux/module.h>
#include <linux/tty.h>
#include <linux/console.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/delay.h>

#include <video/fbcon.h>
#include <video/fbcon-mac1.h>

/* This code is generic for fonts with width less than eight.
 * However, we only have one such font, which is "ProFont6x11".
 * Thus, redefine the font dimension macros as constants to avoid
 * significant pointer dereferencing and memory access overhead.
 */
 
#undef fontwidth(p)
#define fontwidth(p) (6)

#undef fontheight(p)
#define fontheight(p) (11)



void
fbcon_mac_setup1( struct display *p )
{

	if( p->line_length )
	{
		p->next_line = p->line_length;
	}

	else
	{
		p->next_line = p->var.xres_virtual >> 3;
	}

	/* Monochrome frame buffers do not have color planes. */
	p->next_plane = 0;

	/* Check whether our assumption about a 6x11 font is reasonable. */
	if( fontwidth( p ) != p->_fontwidth || fontheight( p ) != p->_fontheight )
	{
		panic( "fbcon_mac_setup1: Unexpected font dimensions %ix%i.\n", fontwidth( p ), fontheight( p ) );
	}

	/* Check whether the horizonal screen resolution is divisible by eight. */
	if( p->var.xres & 7 )
	{
		panic( "fbcon_mac_setup1: Horizonal screen resolution %i is not divisible by eight.\n", p->var.xres );
	}

} /* fbcon_mac_setup1 */



void
fbcon_mac_bmove1 (struct display *p, int sy, int sx, int dy, int dx, int height, int width)
{
	/* Loop variables. */
	int i, j;

	/* The working line source address. */
	u8* sp;
		
	/* The working line destination address. */
	u8* dp;

	/* Address pointers that are used to traverse lines. */
	u8* spw;
	u8* dpw;

	/* The working word. */
	u16 ww;

	/* The delta to the next block line with respect to ps and pd; */
	int lp;

	/* The number of bytes that fully contain the source and destination lines. */
	int si;
	int di;

	/* The destination bytes that contain the block boundaries. */
	u8 dl;
	u8 dr;

	/* Bit masks for bl and br that indicate which bits must be preserved. */
	u8 ml;
	u8 mr;

	/* The direction of the bitshift.  Negative is left, positive is right. */
	int shift;


	if( width == p->conp->vc_cols )
	{
		/* ASSERT: sx == 0  */
		/* ASSERT: sy != dy */

		/* Handle full-width moves, which are usually full-screen scrolls.  */
		/* This method assumes that display lines are byte-aligned.         */

		/* The distance to the next character cell. */
		lp = fontheight( p ) * p->next_line;

		/* Source address. */
		sp = p->screen_base + sy * lp;

		/* Destination address. */
		dp = p->screen_base + dy * lp;

		/* Use fast assembly routines to move the block. */
		mymemmove( dp, sp, height * lp );
		return;

	} /* Full-width block move. */


	/* Change the destination cell coordinates into absolute pixel values. */
	dy *= fontheight( p );
	dx *= fontwidth( p );
	
	/* Change the source cell coordinates into an absolute pixel values. */
	sy *= fontheight( p );
	sx *= fontwidth( p );
	
	/* Change height and width into absolute pixel values. */
	height *= fontheight( p );
	width  *= fontwidth( p );

	/* Determine which bits must be retained in the boundary bytes. */
	ml = 0xFF ^ ( 0xFF >> ( dx & 0x07 ) );
	mr = 0xFF >> ( dx + width ) & 0x07;

	/* Determine the external block width in bytes. */
	si = ( ( sx + width ) >> 3 ) - ( sx >> 3 );
	di = ( ( dx + width ) >> 3 ) - ( dx >> 3 );

	/* ASSERT: abs( si - di ) <= 1 */


	if( sy >= dy )
	{
		/* Upward move.  Start at the top line of the block. */
		sp = p->screen_base + sy * p->next_line;
		dp = p->screen_base + dy * p->next_line;
		lp = p->next_line;
	}

	else
	{
		/* Downward move.  Start at the bottom line of the block. */
		sp = p->screen_base + ( sy + height ) * p->next_line;
		dp = p->screen_base + ( dy + height ) * p->next_line;
		lp = p->next_line * -1;
	}


	if( sx >= dx )
	{
		/* Leftward move.  Advance to the left side of the block. */
		sp += sx >> 3;
		dp += dx >> 3;

		/* Determine which way the bits are shifting within their byte */
		/* with respect to the left boundary for a leftward move. */
		shift = ( dx & 0x07 ) - ( sx & 0x07 );

		if( shift <= 0 )
		{
			/* Make the shift value a positive number. */
			shift *= -1;

			for( j = 0 ; j < height ; j++ )
			{
				/* Reset the working pointers. */
				spw = sp;
				dpw = dp;

				/* Preserve external bits. */
				dl = *( dp      ) & ml;
				dr = *( dp + di ) & mr;

				for( i = 0 ; i < si; i ++ )
				{
					/* Get the next word. */					
					ww =  *(u16*)spw++;

					/* Shift the skewed byte into into the MSB. */
					ww <<= shift;

					/* Shift it back into the LSB. */
					ww >>= 8;

					/* Put the LSB to the destination. */
					*dpw++ = (u8)ww;

				} /* Columns. */


				if( di )
				{
					/* Restore external bits. */
					*( dp      ) = ( *( dp      ) & ~ml ) | dl;
					*( dp + di ) = ( *( dp + di ) & ~mr ) | dr;
				}

				else
				{
					/* Corner case: The destination line is entirely within one byte.      */
					/* eg: ( *sp == '.....xx xxxx....' ) to ( *dp == '.xxxxxx. ........' ) */
					*dp = ( *dp & ~ml & ~mr ) | ( dl & mr );
				}


				/* Go to the next line. */
				sp += lp;
				dp += lp;

			} /* Rows. */

		} /* Leftward move, leftward shift. */

		else
		{
			/* Move the source pointer ahead one byte because.  */
 			/* we will load it as the LSB of the working word. */
			sp -= 1;

			for( j = 0 ; j < height ; j++ )
			{
				/* Reset the working pointers. */
				spw = sp;
				dpw = dp;

				/* Preserve external bits. */
				dl = *( dp      ) & ml;
				dr = *( dp + di ) & mr;

				for( i = 0 ; i < si; i ++ )
				{
					/* Get the next word. */					
					ww =  *(u16*)spw++;

					/* Shift the skewed byte into into the LSB. */
					ww >>= shift;

					/* Mask out the MSB to be safe. */
					ww &= 0x00FF;

					/* Put the LSB to the destination. */
					*dpw++ = (u8)ww;

				} /* Columns. */


				if( di )
				{
					/* Restore external bits. */
					*( dp      ) = ( *( dp      ) & ~ml ) | dl;
					*( dp + di ) = ( *( dp + di ) & ~mr ) | dr;
				}

				else
				{
					/* Corner case: The destination line is entirely within one byte.      */
					/* eg: ( *sp == '.....xx xxxx....' ) to ( *dp == '.xxxxxx. ........' ) */
					*dp = ( *dp & ~ml & ~mr ) | ( dl & mr );
				}


				/* Go to the next line. */
				sp += lp;
				dp += lp;

			} /* Rows. */

		} /* Leftward move, rightward shift. */

	} /* Leftward move. */

	else
	{
		/* Rightward move.  Advance to the right side of the block. */
		sp += ( sx + width ) >> 3;
		dp += ( dx + width ) >> 3;

		/* Determine which way the bits are shifting within their byte */
		/* with respect to the right boundary for a rightward move. */
		shift = ( ( dx + width ) & 0x07 ) - ( ( sx + width ) & 0x07 );

		if( shift > 0 )
		{
			/* Move the source pointer back one byte because.  */
 			/* we will load it as the LSB of the working word. */
			sp -= 1;

			for( j = 0 ; j < height ; j++ )
			{
				/* Reset the working pointers. */
				spw = sp;
				dpw = dp;

				/* Preserve external bits. */
				dl = *( dp - di ) & ml;
				dr = *( dp      ) & mr;

				for( i = 0 ; i < si; i ++ )
				{
					/* Get the next word. */					
					ww =  *(u16*)spw--;

					/* Shift the skewed byte into into the LSB. */
					ww >>= shift;

					/* Mask out the MSB to be safe. */
					ww &= 0x00FF;

					/* Put the LSB to the destination. */
					*dpw-- = (u8)ww;

				} /* Columns. */


				if( di )
				{
					/* Restore external bits. */
					*( dp - di ) = ( *( dp - di ) & ~ml ) | dl;
					*( dp      ) = ( *( dp      ) & ~mr ) | dr;
				}

				else
				{
					/* Corner case: The destination line is entirely within one byte.      */
					/* eg: ( *sp == '.....xx xxxx....' ) to ( *dp == '.xxxxxx. ........' ) */
					*dp = ( *dp & ~ml & ~mr ) | ( dl & mr );
				}


				/* Go to the next line. */
				sp += lp;
				dp += lp;

			} /* Rows. */


		} /* Rightward move, rightward shift. */

		else
		{
			/* Make the shift value a positive number. */
			shift *= -1;
	
			for( j = 0; j < height ; j++ )
			{
				/* Reset the working pointers. */
				spw = sp;
				dpw = dp;

				/* Preserve external bits. */
				dl = *( dp - di ) & ml;
				dr = *( dp      ) & mr;

				for( i = 0 ; i < si ; i++ )
				{
					/* Get the next word. */
					ww = *(u16*)spw--;

					/* Shift the skewed byte into the MSB. */
					ww <<= shift;

					/* Shift the MSB into the LSB. */
					ww >>= 8;

					/* Put the LSB to the destination. */
					*dpw-- = (u8)ww;

				} /* Columns. */


				if( di )
				{
					/* Restore external bits. */
					*( dp - di ) = ( *( dp - di ) & ~ml ) | dl;
					*( dp      ) = ( *( dp      ) & ~mr ) | dr;
				}

				else
				{
					/* Corner case: The destination line is entirely within one byte.      */
					/* eg: ( *sp == '.....xx xxxx....' ) to ( *dp == '.xxxxxx. ........' ) */
					*dp = ( *dp & ~ml & ~mr ) | ( dl & mr );
				}

				/* Go to the next line. */
				sp += lp;
				dp += lp;

			} /* Rows. */

		} /* Rightward move, leftward shift. */

	} /* Rightward move. */

} /* fbcon_mac_bmove1 */


void
fbcon_mac_clear1( struct vc_data *conp, struct display *p, int sy, int sx, int height, int width )
{
	/* Loop indexes. */
	int i, j;

	/* The offset between cell rows in the frame buffer. */
	int cro = p->next_line * fontheight( p );

	if( width == p->conp->vc_cols )
	{
		/* Full-width clearing is a special case that is easy to process quickly. */
		/* ASSERT: sx == 0 */

		if( conp && attr_reverse( p, conp->vc_attr ) )
		{
			/* The console exists and its reverse attribute flag is set. */
 			/* Thus, clear this display with white pixels. */
			mymemset( p->screen_base + sy * cro, height * cro );
			return;
		}

		else	
		{
			/* Clear this display with black pixels. */
			mymemclear( p->screen_base + sy * cro, height * cro );
			return;
		}

	} /* full-width clear */


	/* This is a safe and simple method of clearing arbitrary regions in the */
	/* display that works even if the screen width is not divisble by eight. */

	/* Most calls to this function will clear the entire display anyways, */
	/* so they are handled by the full-width code given above.    */
	
	for( j = sy ; j < sy + height ; j++ )
	{
		for( i = sx ; i < sx + width ; i++ )
		{
			fbcon_mac_putc1( conp, p, ' ', j, i );
		}
	}

} /* fbcon_mac_clear1 */



void
fbcon_mac_putc1( struct vc_data *conp, struct display *p, int c, int yy, int xx )
{
	/* The working frame buffer word. */
	u16 fbw;

	/* The raster for rendering lines in the font. */
	u16 fbr;

	/* The mask that is used to apply the raster to the frame buffer. */
	u16 fbm;

	/* A pointer to a word in the frame buffer that entirely contains one line in the character cell. */
	/* Recall that all display values are in u8, so we must cast fbp into u8 accordingly. */
	u16* fbp = (u16*)p->screen_base;

	/* Get the address of the character image in the font data. */
	u8* dp = p->fontdata + ( c & p->charmask ) * fontheight(p);

	/* The bit offset of the character cell in the working frame buffer word. */
	u8 fbo;

	/* Change the horizontal cell coordinate into a pixel value. */
	xx *= fontwidth(p);

	/* Move the frame buffer pointer up to the first cell row offset. */	
	(u8*)fbp += fontheight(p) * p->next_line * yy;

	/* Move the frame buffer pointer up to the cell column offset. */
	(u8*)fbp += xx >> 3;

	/* Determine the cell offset in the working word by taking it modulo eight. */
	fbo = xx & 0x07;

	/* Create the frame buffer mask for this cell. */
	fbm = 0xFFFF >> fontwidth(p); /* Usually 0000 0011 1111 1111.                 */
	fbm = ~fbm;                   /* Now     1111 1100 0000 0000.                 */
	fbm >>= fbo;                  /* Now     0000 1111 1100 0000, if (fbo == 4).  */
	fbm = ~fbm;                   /* Hence   1111 0000 0011 1111.                 */

	/* Notice how yy is being reused in this loop. */

	for( yy = 0 ; yy < fontheight( p ) ; yy++ )
	{
		/* Get the next line from the character image. */
		fbr = (u16)( *dp++ );

		if( attr_bold( p, c ) )
		{
			/* Make the raster bold with right-side pixel duplication. */
			fbr |= fbr >> 1;
		}
	
		if( attr_reverse( p, c ) /* ^ attr_reverse( p, conp->vc_attr ) */ ) 
		{
			/* Reverse every pixel in the raster. */
			/* fbr = ~fbr; */

			/* Reverse every pixel in the raster except for the padding bits. */
			fbr ^= 0xFFFF << ( 8 - fontwidth( p ) );
		}

		/* Shift the image line left into the MSB. */
		fbr <<= 8;
	
		/* Shift the image right to the target frame buffer offset. */
		fbr >>= fbo;

		/* Get the word that is already in the frame buffer. */
		fbw = *fbp;
	
		/* Mask bits in the word that we need to keep. */
		fbw  &= fbm;
	
		/* Add the bits from the character image to the bits already in the frame buffer. */
		fbw |= fbr;
	
		/* Write this word back into the frame buffer. */
		*fbp = fbw;
	
		/* Increment the frame buffer pointer to the next row of pixels. */
		(u8*)fbp += p->next_line;

	} /* for */

	if( attr_underline( p, c ) )
	{
		/* Move the frame buffer pointer to the second-last line of the character cell. */
		(u8*)fbp -= p->next_line;
		(u8*)fbp -= p->next_line;

		/* Underscore the character. */
		*fbp ^= ~fbm;

	} /* underlining */

} /* fbcon_mac_putc1 */



void
fbcon_mac_putcs1 (struct vc_data *conp, struct display *p, const unsigned short *s, int count, int yy, int xx)
{
	u16 c;

	while( count-- )
	{
		c = scr_readw( s++ );
		fbcon_mac_putc1( conp, p, c, yy, xx++ );
	}

} /* fbcon_mac_putcs1 */



void
fbcon_mac_revc1( struct display *p, int xx, int yy )
{
	/* Invert the pixels in the character cell at coordinates (xx,yy). */
	/* This function is [only] used to blink a soft cursor. */

	/* The mask that is used to apply the raster to the frame buffer. */
	u16 fbm;

	/* A pointer to a word in the frame buffer that entirely contains one line in the character cell. */
	/* Recall that all display values are in u8, so we must cast fbp into u8 accordingly. */
	u16* fbp = (u16*)p->screen_base;

	/* Change the horizontal cell coordinate into a pixel value. */
	xx *= fontwidth(p);

	/* Move the frame buffer pointer up to the first cell row offset. */	
	(u8*)fbp += fontheight(p) * p->next_line * yy;

	/* Move the frame buffer pointer up to the cell column offset. */
	(u8*)fbp += xx >> 3;

	/* Create the frame buffer mask of bits that must be reversed. */
	fbm = 0xFFFF >> fontwidth(p); /* Usually 0000 0011 1111 1111.                 */
	fbm = ~fbm;                   /* Now     1111 1100 0000 0000.                 */

	/* Shift the bit mask to the cell offset in the word.  */
	fbm >>= xx & 0x07;            /* Now     0000 1111 1100 0000, if xx mod 8 == 4.  */

	/* Notice how yy and c are being reused in these loops. */

	for( yy = 0 ; yy < fontheight( p ) ; yy++ )
	{
		/* Invert all bits in this line of the character cell. */
		/* *fbp ^= fbm; */
		*fbp = *fbp ^ fbm;
	
		/* Increment the frame buffer pointer to the next row of pixels. */
		(u8*)fbp += p->next_line;
	
	} /* for */

} /* fbcon_mac_revc1 */



/* The display_switch contains our function bindings for low-level fbcon operations */

struct display_switch fbcon_mac1 =
{
	fbcon_mac_setup1,
	fbcon_mac_bmove1,
	fbcon_mac_clear1,
	fbcon_mac_putc1,
	fbcon_mac_putcs1,
	fbcon_mac_revc1,
	NULL,
	NULL,
	NULL,
	FONTWIDTHRANGE (1, 8)
};



/* Begin kernel module boilerplate. */

#ifdef MODULE
int
init_module (void)
{
  return 0;
}

void
cleanup_module (void)
{
	/* Do nothing. */
}
#endif /* MODULE */

/*  Visible symbols for kernel modules. */
EXPORT_SYMBOL (fbcon_mac1);
EXPORT_SYMBOL (fbcon_mac_setup1);
EXPORT_SYMBOL (fbcon_mac_bmove1);
EXPORT_SYMBOL (fbcon_mac_clear1);
EXPORT_SYMBOL (fbcon_mac_putc1);
EXPORT_SYMBOL (fbcon_mac_putcs1);
EXPORT_SYMBOL (fbcon_mac_revc1);

/* End kernel module boilerplate. */

/* eof */
