Subject: [patch 7/7] xenon: add framebuffer support (ugly)

From: Felix Domke <[email-masked]>
Date: Wed, 07 Mar 2007 19:01:51 +0100

This adds support for a framebuffer on the "Xenos" graphics chip used in the
Xbox 360 machine. It requires a pre-initialized framebuffer.

The major problem is that the graphics chip only supports a 'tiled' mode,
thus this patch also uses some serious ugly patches to allow the framebuffer
console to handle this. So this patch is given just for completeness, to have 
a working console output, and of course not meant for inclusion into the 
kernel.

This patch can probably be rewritten using the deferred io support in a
much nicer way.

Signed-off-by: Felix Domke <[email-masked]>

---
 drivers/video/Kconfig     |    9 +
 drivers/video/Makefile    |    1 
 drivers/video/cfbimgblt.c |   23 +--
 drivers/video/xenonfb.c   |  337 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/fb.h        |   12 +
 5 files changed, 373 insertions(+), 9 deletions(-)

Index: linux-2.6.20/drivers/video/Kconfig
===================================================================
--- linux-2.6.20.orig/drivers/video/Kconfig	2007-03-07 19:01:12.000000000 +0100
+++ linux-2.6.20/drivers/video/Kconfig	2007-03-07 19:01:23.000000000 +0100
@@ -568,6 +568,15 @@
 	help
 	  This is the frame buffer device driver for the Intel-based Macintosh
 
+config FB_XENON
+	bool "Xbox 360 Framebuffer Support"
+	depends on (FB = y) && PPC
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	help
+	  This is the frame buffer device driver for the Microsoft Xbox 360.
+
 config FB_HGA
 	tristate "Hercules mono graphics support"
 	depends on FB && X86
Index: linux-2.6.20/drivers/video/Makefile
===================================================================
--- linux-2.6.20.orig/drivers/video/Makefile	2007-03-07 19:01:12.000000000 +0100
+++ linux-2.6.20/drivers/video/Makefile	2007-03-07 19:01:23.000000000 +0100
@@ -104,6 +104,7 @@
 # Platform or fallback drivers go here
 obj-$(CONFIG_FB_VESA)             += vesafb.o
 obj-$(CONFIG_FB_IMAC)             += imacfb.o
+obj-$(CONFIG_FB_XENON)            += xenonfb.o
 obj-$(CONFIG_FB_VGA16)            += vga16fb.o vgastate.o
 obj-$(CONFIG_FB_OF)               += offb.o
 
Index: linux-2.6.20/drivers/video/cfbimgblt.c
===================================================================
--- linux-2.6.20.orig/drivers/video/cfbimgblt.c	2007-03-07 19:01:12.000000000 +0100
+++ linux-2.6.20/drivers/video/cfbimgblt.c	2007-03-07 19:01:23.000000000 +0100
@@ -72,8 +72,13 @@
 	0x00000000, 0xffffffff
 };
 
-#define FB_WRITEL fb_writel
-#define FB_READL  fb_readl
+#if 0
+#define FB_WRITEL(p,b,addr) fb_writel(b,addr)
+#define FB_READL(p,addr)  fb_readl(addr)
+#else
+#define FB_READL(p,addr)  fb_readl(xenon_convert(p, addr))
+#define FB_WRITEL(p,b,addr) fb_writel(b, xenon_convert(p, addr))
+#endif
 
 static inline void color_imageblit(const struct fb_image *image, 
 				   struct fb_info *p, u8 __iomem *dst1, 
@@ -97,7 +102,7 @@
 		
 		if (start_index) {
 			u32 start_mask = ~(FB_SHIFT_HIGH(~(u32)0, start_index));
-			val = FB_READL(dst) & start_mask;
+			val = FB_READL(p, dst) & start_mask;
 			shift = start_index;
 		}
 		while (n--) {
@@ -109,7 +114,7 @@
 			color <<= FB_LEFT_POS(bpp);
 			val |= FB_SHIFT_HIGH(color, shift);
 			if (shift >= null_bits) {
-				FB_WRITEL(val, dst++);
+				FB_WRITEL(p, val, dst++);
 	
 				val = (shift == null_bits) ? 0 : 
 					FB_SHIFT_LOW(color, 32 - shift);
@@ -121,7 +126,7 @@
 		if (shift) {
 			u32 end_mask = FB_SHIFT_HIGH(~(u32)0, shift);
 
-			FB_WRITEL((FB_READL(dst) & end_mask) | val, dst);
+			FB_WRITEL(p, (FB_READL(p, dst) & end_mask) | val, dst);
 		}
 		dst1 += p->fix.line_length;
 		if (pitch_index) {
@@ -162,7 +167,7 @@
 		/* write leading bits */
 		if (start_index) {
 			u32 start_mask = ~(FB_SHIFT_HIGH(~(u32)0,start_index));
-			val = FB_READL(dst) & start_mask;
+			val = FB_READL(p, dst) & start_mask;
 			shift = start_index;
 		}
 
@@ -173,7 +178,7 @@
 			
 			/* Did the bitshift spill bits to the next long? */
 			if (shift >= null_bits) {
-				FB_WRITEL(val, dst++);
+				FB_WRITEL(p, val, dst++);
 				val = (shift == null_bits) ? 0 :
 					FB_SHIFT_LOW(color,32 - shift);
 			}
@@ -186,7 +191,7 @@
  		if (shift) {
 			u32 end_mask = FB_SHIFT_HIGH(~(u32)0, shift);
 
-			FB_WRITEL((FB_READL(dst) & end_mask) | val, dst);
+			FB_WRITEL(p, (FB_READL(p, dst) & end_mask) | val, dst);
 		}
 		
 		dst1 += pitch;
@@ -251,7 +256,7 @@
 		for (j = k; j--; ) {
 			shift -= ppw;
 			end_mask = tab[(*src >> shift) & bit_mask];
-			FB_WRITEL((end_mask & eorx)^bgx, dst++);
+			FB_WRITEL(p, (end_mask & eorx)^bgx, dst++);
 			if (!shift) { shift = 8; src++; }		
 		}
 		dst1 += p->fix.line_length;
Index: linux-2.6.20/include/linux/fb.h
===================================================================
--- linux-2.6.20.orig/include/linux/fb.h	2007-03-07 19:01:12.000000000 +0100
+++ linux-2.6.20/include/linux/fb.h	2007-03-07 19:01:23.000000000 +0100
@@ -907,6 +907,18 @@
 	}
 }
 
+static inline int *xenon_convert(struct fb_info *p, int *addr)
+{
+	int index = ((char*)addr) - ((char*)p->screen_base);
+	int y = index / (p->fix.line_length);
+	int x = index % (p->fix.line_length)/4;
+	unsigned int base = ((((y & ~31)*p->var.xres) + (x & ~31)*32 ) +
+	 (((x&3) + ((y&1)<<2) + ((x&28)<<1) + ((y&30)<<5)) ^ ((y&8)<<2))) * 4;
+
+	return (int*)(((char*)p->screen_base)+base);
+}
+
+
 /* drivers/video/fbsysfs.c */
 extern struct fb_info *framebuffer_alloc(size_t size, struct device *dev);
 extern void framebuffer_release(struct fb_info *info);
Index: linux-2.6.20/drivers/video/xenonfb.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.20/drivers/video/xenonfb.c	2007-03-07 19:01:23.000000000 +0100
@@ -0,0 +1,337 @@
+/*
+ * framebuffer driver for Microsoft Xbox 360
+ *
+ * (c) 2006 ...
+ * Original vesafb driver written by Gerd Knorr <[email-masked]>
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/screen_info.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/dmi.h>
+
+#include <asm/io.h>
+
+#include <video/vga.h>
+
+/* --------------------------------------------------------------------- */
+
+static struct fb_var_screeninfo xenonfb_defined __initdata = {
+	.activate		= FB_ACTIVATE_NOW,
+	.height			= -1,
+	.width			= -1,
+	.right_margin		= 32,
+	.upper_margin		= 16,
+	.lower_margin		= 4,
+	.vsync_len		= 4,
+	.vmode			= FB_VMODE_NONINTERLACED,
+};
+
+static struct fb_fix_screeninfo xenonfb_fix __initdata = {
+	.id			= "XENON FB",
+	.type			= FB_TYPE_PACKED_PIXELS,
+	.accel			= FB_ACCEL_NONE,
+	.visual			= FB_VISUAL_TRUECOLOR,
+};
+
+typedef struct {
+	uint32_t unknown1[4];
+	uint32_t base;
+	uint32_t unknown2[8];
+	uint32_t width;
+	uint32_t height;
+} ati_info;
+
+#define	DEFAULT_FB_MEM	1024*1024*16
+
+/* --------------------------------------------------------------------- */
+
+static int xenonfb_setcolreg(unsigned regno, unsigned red, unsigned green,
+			    unsigned blue, unsigned transp,
+			    struct fb_info *info)
+{
+	/*
+	 *  Set a single color register. The values supplied are
+	 *  already rounded down to the hardware's capabilities
+	 *  (according to the entries in the `var' structure). Return
+	 *  != 0 for invalid regno.
+	 */
+
+	if (regno >= info->cmap.len)
+		return 1;
+
+	if (regno < 16) {
+		red   >>= 8;
+		green >>= 8;
+		blue  >>= 8;
+		((u32 *)(info->pseudo_palette))[regno] =
+			(red   << info->var.red.offset)   |
+			(green << info->var.green.offset) |
+			(blue  << info->var.blue.offset);
+	}
+	return 0;
+}
+
+#define XENON_XY_TO_STD_PTR(x,y) ((int*)(((char*)p->screen_base)+y*p->fix.line_length+x*(p->var.bits_per_pixel/8)))
+#define XENON_XY_TO_XENON_PTR(x,y) xenon_convert(p, XENON_XY_TO_STD_PTR(x,y))
+
+inline void xenon_pset(struct fb_info *p, int x, int y, int color)
+{
+	fb_writel(color, XENON_XY_TO_XENON_PTR(x,y));
+}
+
+inline int xenon_pget(struct fb_info *p, int x, int y)
+{
+	return fb_readl(XENON_XY_TO_XENON_PTR(x,y));
+}
+
+void xenon_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
+{
+
+	__u32 x, y;
+	for (y=0; y<rect->height; y++) {
+		for (x=0; x<rect->width; x++) {
+			xenon_pset(p, rect->dx+x, rect->dy+y, rect->color);
+
+		}
+	}
+}
+
+void xenon_copyarea(struct fb_info *p, const struct fb_copyarea *area)
+{
+
+	/* if the beginning of the target area might overlap with the end of
+	the source area, be have to copy the area reverse. */
+	if ((area->dy == area->sy && area->dx > area->sx) || (area->dy > area->sy)) {
+		__s32 x, y;
+		for (y=area->height-1; y>0; y--) {
+			for (x=area->width-1; x>0; x--) {
+				xenon_pset(p, area->dx+x, area->dy+y, xenon_pget(p, area->sx+x, area->sy+y));
+			}
+		}
+	} else {
+		__u32 x, y;
+		for (y=0; y<area->height; y++) {
+			for (x=0; x<area->width; x++) {
+				xenon_pset(p, area->dx+x, area->dy+y, xenon_pget(p, area->sx+x, area->sy+y));
+			}
+		}
+	}
+}
+
+static struct fb_ops xenonfb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_setcolreg	= xenonfb_setcolreg,
+	.fb_fillrect	= xenon_fillrect,
+	.fb_copyarea	= xenon_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+};
+
+static int __init xenonfb_probe(struct platform_device *dev)
+{
+	struct fb_info *info;
+	int err;
+	unsigned int size_vmode;
+	unsigned int size_remap;
+	unsigned int size_total;
+
+	screen_info.lfb_depth = 32;
+	screen_info.lfb_size = DEFAULT_FB_MEM / 0x10000;
+	screen_info.pages=1;
+	screen_info.blue_size = 8;
+	screen_info.blue_pos = 24;
+	screen_info.green_size = 8;
+	screen_info.green_pos = 16;
+	screen_info.red_size = 8;
+	screen_info.red_pos = 8;
+	screen_info.rsvd_size = 8;
+	screen_info.rsvd_pos = 0;
+
+	ati_info *ai = ioremap(0x200ec806100ULL, sizeof(ati_info));
+
+
+
+
+
+
+
+	screen_info.lfb_base = ai->base;
+	screen_info.lfb_width = ai->width;
+	screen_info.lfb_height = ai->height;
+	screen_info.lfb_linelength = screen_info.lfb_width * screen_info.lfb_depth/4;
+
+	printk(KERN_INFO "xenonfb: detected %dx%d framebuffer @ 0x%08x\n", screen_info.lfb_width, screen_info.lfb_height, screen_info.lfb_base);
+
+
+	iounmap(ai);
+
+	xenonfb_fix.smem_start = screen_info.lfb_base;
+	xenonfb_defined.bits_per_pixel = screen_info.lfb_depth;
+	xenonfb_defined.xres = screen_info.lfb_width;
+	xenonfb_defined.yres = screen_info.lfb_height;
+	xenonfb_defined.xoffset = 0;
+	xenonfb_defined.yoffset = 0;
+	xenonfb_fix.line_length = screen_info.lfb_linelength;
+
+	/*   size_vmode -- that is the amount of memory needed for the
+	 *                 used video mode, i.e. the minimum amount of
+	 *                 memory we need. */
+	size_vmode = xenonfb_defined.yres * xenonfb_fix.line_length;
+
+	/*   size_total -- all video memory we have. Used for
+	 *                 entries, ressource allocation and bounds
+	 *                 checking. */
+	size_total = screen_info.lfb_size * 65536;
+	if (size_total < size_vmode)
+		size_total = size_vmode;
+
+	/*   size_remap -- the amount of video memory we are going to
+	 *                 use for xenonfb.  With modern cards it is no
+	 *                 option to simply use size_total as that
+	 *                 wastes plenty of kernel address space. */
+	size_remap  = size_vmode * 2;
+	if (size_remap < size_vmode)
+		size_remap = size_vmode;
+	if (size_remap > size_total)
+		size_remap = size_total;
+	xenonfb_fix.smem_len = size_remap;
+
+	if (!request_mem_region(xenonfb_fix.smem_start, size_total, "xenonfb")) {
+		printk(KERN_WARNING
+		       "xenonfb: cannot reserve video memory at 0x%lx\n",
+			xenonfb_fix.smem_start);
+		/* We cannot make this fatal. Sometimes this comes from magic
+		   spaces our resource handlers simply don't know about */
+	}
+
+	info = framebuffer_alloc(sizeof(u32) * 16, &dev->dev);
+	if (!info) {
+		err = -ENOMEM;
+		goto err_release_mem;
+	}
+	info->pseudo_palette = info->par;
+	info->par = NULL;
+
+	info->screen_base = ioremap(xenonfb_fix.smem_start, xenonfb_fix.smem_len);
+	if (!info->screen_base) {
+		printk(KERN_ERR "xenonfb: abort, cannot ioremap video memory "
+				"0x%x @ 0x%lx\n",
+			xenonfb_fix.smem_len, xenonfb_fix.smem_start);
+		err = -EIO;
+		goto err_unmap;
+	}
+
+	printk(KERN_INFO "xenonfb: framebuffer at 0x%lx, mapped to 0x%p, "
+	       "using %dk, total %dk\n",
+	       xenonfb_fix.smem_start, info->screen_base,
+	       size_remap/1024, size_total/1024);
+	printk(KERN_INFO "xenonfb: mode is %dx%dx%d, linelength=%d, pages=%d\n",
+	       xenonfb_defined.xres, xenonfb_defined.yres,
+	       xenonfb_defined.bits_per_pixel, xenonfb_fix.line_length,
+	       screen_info.pages);
+
+	xenonfb_defined.xres_virtual = xenonfb_defined.xres;
+	xenonfb_defined.yres_virtual = xenonfb_fix.smem_len /
+					xenonfb_fix.line_length;
+	printk(KERN_INFO "xenonfb: scrolling: redraw\n");
+	xenonfb_defined.yres_virtual = xenonfb_defined.yres;
+
+	/* some dummy values for timing to make fbset happy */
+	xenonfb_defined.pixclock     = 10000000 / xenonfb_defined.xres *
+					1000 / xenonfb_defined.yres;
+	xenonfb_defined.left_margin  = (xenonfb_defined.xres / 8) & 0xf8;
+	xenonfb_defined.hsync_len    = (xenonfb_defined.xres / 8) & 0xf8;
+
+	xenonfb_defined.red.offset    = screen_info.red_pos;
+	xenonfb_defined.red.length    = screen_info.red_size;
+	xenonfb_defined.green.offset  = screen_info.green_pos;
+	xenonfb_defined.green.length  = screen_info.green_size;
+	xenonfb_defined.blue.offset   = screen_info.blue_pos;
+	xenonfb_defined.blue.length   = screen_info.blue_size;
+	xenonfb_defined.transp.offset = screen_info.rsvd_pos;
+	xenonfb_defined.transp.length = screen_info.rsvd_size;
+
+	printk(KERN_INFO "xenonfb: %s: "
+	       "size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n",
+	       "Truecolor",
+	       screen_info.rsvd_size,
+	       screen_info.red_size,
+	       screen_info.green_size,
+	       screen_info.blue_size,
+	       screen_info.rsvd_pos,
+	       screen_info.red_pos,
+	       screen_info.green_pos,
+	       screen_info.blue_pos);
+
+	xenonfb_fix.ypanstep  = 0;
+	xenonfb_fix.ywrapstep = 0;
+
+	/* request failure does not faze us, as vgacon probably has this
+	 * region already (FIXME) */
+	request_region(0x3c0, 32, "xenonfb");
+
+	info->fbops = &xenonfb_ops;
+	info->var = xenonfb_defined;
+	info->fix = xenonfb_fix;
+	info->flags = FBINFO_FLAG_DEFAULT;
+
+	if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) {
+		err = -ENOMEM;
+		goto err_unmap;
+	}
+	if (register_framebuffer(info)<0) {
+		err = -EINVAL;
+		goto err_fb_dealoc;
+	}
+	printk(KERN_INFO "fb%d: %s frame buffer device\n",
+	       info->node, info->fix.id);
+	return 0;
+
+err_fb_dealoc:
+	fb_dealloc_cmap(&info->cmap);
+err_unmap:
+	iounmap(info->screen_base);
+	framebuffer_release(info);
+err_release_mem:
+	release_mem_region(xenonfb_fix.smem_start, size_total);
+	return err;
+}
+
+static struct platform_driver xenonfb_driver = {
+	.probe	= xenonfb_probe,
+	.driver	= {
+		.name	= "xenonfb",
+	},
+};
+
+static struct platform_device xenonfb_device = {
+	.name	= "xenonfb",
+};
+
+static int __init xenonfb_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&xenonfb_driver);
+
+	if (!ret) {
+		ret = platform_device_register(&xenonfb_device);
+		if (ret)
+			platform_driver_unregister(&xenonfb_driver);
+	}
+	return ret;
+}
+module_init(xenonfb_init);
+
+MODULE_LICENSE("GPL");
+

--