diff mbox series

[094/120] MIPS: PS2: FB: Frame buffer driver for the PlayStation 2

Message ID 4927c42fb3401c42c4c5a077f272331ac79d80b1.1567326213.git.noring@nocrew.org (mailing list archive)
State RFC
Headers show
Series Linux for the PlayStation 2 | expand

Commit Message

Fredrik Noring Sept. 1, 2019, 4:26 p.m. UTC
The main limitation is the lack of mmap, since the Graphics Synthesizer
has local frame buffer memory that is not directly accessible from the
main bus. The GS has 4 MiB of local memory.

The console drawing primitives are synchronous to allow printk at any
time. This is highly useful for debugging but it is not the fastest
possible implementation. The console is nevertheless very fast and
makes use of several hardware accelerated features of the Graphics
Synthesizer.

The maximum practical resolution is 1920x1080p at 16 bits per pixel that
requires 4147200 bytes of local memory, leaving 47104 bytes for a tiled
font, which at 8x8 pixels and a minimum 4 bits indexed texture palette is
at most 1464 characters. The indexed palette makes switching colours easy.
&struct fb_tile_ops is accelerated with GS texture sprites that are fast
(GS local copy) for the kernel via simple DMA GS commands via the GIF.

Signed-off-by: Fredrik Noring <noring@nocrew.org>
---
 drivers/video/fbdev/Kconfig    |  12 +
 drivers/video/fbdev/Makefile   |   1 +
 drivers/video/fbdev/ps2fb.c    | 533 +++++++++++++++++++++++++++++++++
 include/linux/console_struct.h |   2 +
 include/uapi/linux/fb.h        |   1 +
 5 files changed, 549 insertions(+)
 create mode 100644 drivers/video/fbdev/ps2fb.c

Comments

Jiaxun Yang Sept. 2, 2019, 1:12 a.m. UTC | #1
在 2019/9/2 0:26, Fredrik Noring 写道:
> The main limitation is the lack of mmap, since the Graphics Synthesizer
> has local frame buffer memory that is not directly accessible from the
> main bus. The GS has 4 MiB of local memory.
>
> The console drawing primitives are synchronous to allow printk at any
> time. This is highly useful for debugging but it is not the fastest
> possible implementation. The console is nevertheless very fast and
> makes use of several hardware accelerated features of the Graphics
> Synthesizer.
>
> The maximum practical resolution is 1920x1080p at 16 bits per pixel that
> requires 4147200 bytes of local memory, leaving 47104 bytes for a tiled
> font, which at 8x8 pixels and a minimum 4 bits indexed texture palette is
> at most 1464 characters. The indexed palette makes switching colours easy.
> &struct fb_tile_ops is accelerated with GS texture sprites that are fast
> (GS local copy) for the kernel via simple DMA GS commands via the GIF.
>
> Signed-off-by: Fredrik Noring <noring@nocrew.org>

Hi Fredik,

According to kernel policy[1] no more new FBDev driver would be accepted.

Please refactor it to DRM.

Thanks.

[1] http://lkml.iu.edu/hypermail/linux/kernel/1509.3/00253.html

--

Jiaxun Yang

> ---
>   drivers/video/fbdev/Kconfig    |  12 +
>   drivers/video/fbdev/Makefile   |   1 +
>   drivers/video/fbdev/ps2fb.c    | 533 +++++++++++++++++++++++++++++++++
>   include/linux/console_struct.h |   2 +
>   include/uapi/linux/fb.h        |   1 +
>   5 files changed, 549 insertions(+)
>   create mode 100644 drivers/video/fbdev/ps2fb.c
>
> diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
> index 6b2de93bd302..cc93cbd67b01 100644
> --- a/drivers/video/fbdev/Kconfig
> +++ b/drivers/video/fbdev/Kconfig
> @@ -1999,6 +1999,18 @@ config FB_IBM_GXT4500
>   	  doesn't use Geometry Engine GT1000. This driver also supports
>   	  AGP Fire GL2/3/4 cards on x86.
>   
> +# FIXME FB_SYS_*
> +config FB_PS2
> +	tristate "Frame buffer driver for Sony Playstation 2"
> +	depends on FB && SONY_PS2
> +	select PS2_GS
> +	select FB_TILEBLITTING
> +	default y
> +	help
> +	  Frame buffer driver for the Sony Playstation 2 Graphics Synthesizer.
> +	  Memory mapping is not supported since the frame buffer is local to
> +	  the GS.
> +
>   config FB_PS3
>   	tristate "PS3 GPU framebuffer driver"
>   	depends on FB && PS3_PS3AV
> diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
> index 7dc4861a93e6..1e55fa8ca4af 100644
> --- a/drivers/video/fbdev/Makefile
> +++ b/drivers/video/fbdev/Makefile
> @@ -105,6 +105,7 @@ obj-$(CONFIG_FB_S3C2410)	  += s3c2410fb.o
>   obj-$(CONFIG_FB_FSL_DIU)	  += fsl-diu-fb.o
>   obj-$(CONFIG_FB_COBALT)           += cobalt_lcdfb.o
>   obj-$(CONFIG_FB_IBM_GXT4500)	  += gxt4500.o
> +obj-$(CONFIG_FB_PS2)		  += ps2fb.o
>   obj-$(CONFIG_FB_PS3)		  += ps3fb.o
>   obj-$(CONFIG_FB_SM501)            += sm501fb.o
>   obj-$(CONFIG_FB_UDL)		  += udlfb.o
> diff --git a/drivers/video/fbdev/ps2fb.c b/drivers/video/fbdev/ps2fb.c
> new file mode 100644
> index 000000000000..7bfbc3c2aa4d
> --- /dev/null
> +++ b/drivers/video/fbdev/ps2fb.c
> @@ -0,0 +1,533 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * PlayStation 2 frame buffer driver
> + *
> + * Copyright (C) 2019 Fredrik Noring
> + */
> +
> +/**
> + * DOC: The PlayStation 2 frame buffer console
> + *
> + * The frame buffer supports a tiled frame buffer console. The main limitation
> + * is the lack of memory mapping (mmap), since the Graphics Synthesizer has
> + * local frame buffer memory that is not directly accessible from the main bus.
> + * The GS has 4 MiB of local memory.
> + *
> + * The console drawing primitives are synchronous to allow printk at any time.
> + * This is highly useful for debugging but it is not the fastest possible
> + * implementation. The console is nevertheless very fast and makes use of
> + * several hardware accelerated features of the Graphics Synthesizer.
> + *
> + * The maximum practical resolution is 1920x1080p at 16 bits per pixel that
> + * requires 4147200 bytes of local memory, leaving 47104 bytes for a tiled
> + * font, which at 8x8 pixels and a minimum 4 bits indexed texture palette is
> + * at most 1464 characters. The indexed palette makes switching colours easy.
> + * &struct fb_tile_ops is accelerated with GS texture sprites that are fast
> + * (GS local copy) for the kernel via simple DMA GS commands via the GIF.
> + *
> + * The local memory is organised as follows: first comes the display buffer,
> + * then one block of a palette, and finally the font installed as a texture.
> + *
> + * All frame buffer transmissions are done by DMA via GIF PATH3.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/errno.h>
> +#include <linux/fb.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/string.h>
> +#include <linux/uaccess.h>
> +
> +#include <asm/io.h>
> +
> +#include <asm/mach-ps2/dmac.h>
> +#include <asm/mach-ps2/gif.h>
> +#include <asm/mach-ps2/gs.h>
> +#include <asm/mach-ps2/gs-registers.h>
> +
> +#include <uapi/asm/gif.h>
> +#include <uapi/asm/gs.h>
> +
> +#define DEVICE_NAME "ps2fb"
> +
> +#define PALETTE_BLOCK_COUNT 1	/* One block is used for the indexed colors */
> +
> +/* Module parameters */
> +static char *mode_option;
> +
> +union package {
> +	union gif_data gif;
> +	struct dma_tag dma;
> +};
> +
> +/**
> + * struct tile_texture - texture representing a tile
> + * @tbp: texture base pointer
> + * @u: texel u coordinate (x coordinate)
> + * @v: texel v coordinate (y coordinate)
> + */
> +struct tile_texture {
> +	u32 tbp;
> +	u32 u;
> +	u32 v;
> +};
> +
> +/**
> + * struct console_buffer - console buffer
> + * @block_count: number of frame buffer blocks
> + * @bg: background color index
> + * @fg: foreground color index
> + * @tile: tile dimensions
> + * @tile.width: width in pixels
> + * @tile.height: height in pixels
> + * @tile.width2: least width in pixels, power of 2
> + * @tile.height2: least height in pixels, power of 2
> + * @tile.block: tiles are stored as textures in the PSMT4 pixel storage format
> + * 	with both cols and rows as powers of 2
> + * @tile.block.cols: tile columns per GS block
> + * @tile.block.rows: tile rows per GS block
> + */
> +struct console_buffer {
> +	u32 block_count;
> +
> +	u32 bg;
> +	u32 fg;
> +
> +	struct cb_tile {
> +		u32 width;
> +		u32 height;
> +
> +		u32 width2;
> +		u32 height2;
> +
> +		struct {
> +			u32 cols;
> +			u32 rows;
> +		} block;
> +	} tile;
> +};
> +
> +/**
> + * struct ps2fb_par - driver specific structure
> + * @lock: spin lock to be taken for all structure operations
> + * @cb: console buffer definition
> + * @package: tags and datafor the GIF
> + * @package.capacity: maximum number of GIF packages in 16-byte unit
> + * @package.buffer: DMA buffer for GIF packages
> + */
> +struct ps2fb_par {
> +	spinlock_t lock;
> +
> +	struct console_buffer cb;
> +
> +	struct {
> +		size_t capacity;
> +		union package *buffer;
> +	} package;
> +};
> +
> +/**
> + * texture_least_power_of_2 - round up to a power of 2, not less than 8
> + * @n: integer to round up
> + *
> + * Return: least integer that is a power of 2 and not less than @n or 8
> + */
> +static u32 texture_least_power_of_2(u32 n)
> +{
> +	return max(1 << get_count_order(n), 8);
> +}
> +
> +/**
> + * cb_tile - create a console buffer tile object
> + * @width: width of tile in pixels
> + * @height: height of tile in pixels
> + *
> + * Return: a console buffer tile object with the given width and height
> + */
> +static struct cb_tile cb_tile(u32 width, u32 height)
> +{
> +	const u32 width2 = texture_least_power_of_2(width);
> +	const u32 height2 = texture_least_power_of_2(height);
> +
> +	return (struct cb_tile) {
> +		.width = width,
> +		.height = height,
> +
> +		.width2 = width2,
> +		.height2 = height2,
> +
> +		.block = {
> +			.cols = GS_PSMT4_BLOCK_WIDTH / width2,
> +			.rows = GS_PSMT4_BLOCK_HEIGHT / height2,
> +		},
> +	};
> +}
> +
> +/**
> + * display_buffer_size - display buffer size for a given video resolution
> + *
> + * This calculation is a lower bound estimate. A precise calculation would have
> + * to take memory pages, blocks and column arrangements into account. To choose
> + * the appropriate standard video mode such details can be disregarded, though.
> + *
> + * Return: the size in bytes of the display buffer
> + */
> +static u32 display_buffer_size(const u32 xres_virtual, const u32 yres_virtual,
> +      const u32 bits_per_pixel)
> +{
> +	return (xres_virtual * yres_virtual * bits_per_pixel) / 8;
> +}
> +
> +/**
> + * ps2fb_cb_get_tilemax - maximum number of tiles
> + * @info: frame buffer info object
> + *
> + * Return: the maximum number of tiles
> + */
> +static int ps2fb_cb_get_tilemax(struct fb_info *info)
> +{
> +	const struct ps2fb_par *par = info->par;
> +	const u32 block_tile_count =
> +		par->cb.tile.block.cols *
> +		par->cb.tile.block.rows;
> +	const s32 blocks_available =
> +		GS_BLOCK_COUNT - par->cb.block_count - PALETTE_BLOCK_COUNT;
> +
> +	return blocks_available > 0 ? blocks_available * block_tile_count : 0;
> +}
> +
> +/**
> + * bits_per_pixel_fits - does the given resolution fit the given buffer size?
> + * @xres_virtual: virtual x resolution in pixels
> + * @yres_virtual: virtual y resolution in pixels
> + * @bits_per_pixel: number of bits per pixel
> + * @buffer_size: size in bytes of display buffer
> + *
> + * The size calculation is approximate, but accurate enough for the standard
> + * video modes.
> + *
> + * Return: %true if the resolution fits the given buffer size, otherwise %false
> + */
> +static bool bits_per_pixel_fits(const u32 xres_virtual, const u32 yres_virtual,
> +      const int bits_per_pixel, const size_t buffer_size)
> +{
> +	return display_buffer_size(xres_virtual, yres_virtual,
> +		bits_per_pixel) <= buffer_size;
> +}
> +
> +/**
> + * default_bits_per_pixel - choose either 16 or 32 bits per pixel
> + * @xres_virtual: virtual x resolution in pixels
> + * @yres_virtual: virtual y resolution in pixels
> + * @buffer_size: size in bytes of display buffer
> + *
> + * 32 bits per pixel is returned unless this does not fit the given buffer size.
> + *
> + * The size calculation is approximate, but accurate enough for the standard
> + * video modes.
> + *
> + * Return: 16 or 32 bits per pixel
> + */
> +static int default_bits_per_pixel(
> +	const u32 xres_virtual, const u32 yres_virtual,
> +	const size_t buffer_size)
> +{
> +	return bits_per_pixel_fits(xres_virtual, yres_virtual,
> +		32, buffer_size) ? 32 : 16;
> +}
> +
> +/**
> + * filled_var_videomode - is the screen info video mode filled in?
> + * @var: screen info object to check
> + *
> + * Return: %true if the video mode is filled in, otherwise %false
> + */
> +static bool filled_var_videomode(const struct fb_var_screeninfo *var)
> +{
> +	return var->xres > 0 && var->hsync_len > 0 &&
> +	       var->yres > 0 && var->vsync_len > 0 && var->pixclock > 0;
> +}
> +
> +static int ps2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
> +{
> +	/* Check whether video mode defaults are needed. */
> +	if (!filled_var_videomode(var)) {
> +		const struct fb_videomode *vm =
> +			fb_find_best_mode(var, &info->modelist);
> +
> +		if (!vm)
> +			return -EINVAL;
> +
> +		fb_videomode_to_var(var, vm);
> +	}
> +
> +        /* GS video register resolution is limited to 2048. */
> +        if (var->xres < 1 || 2048 < var->xres ||
> +	    var->yres < 1 || 2048 < var->yres)
> +		return -EINVAL;
> +
> +	var->xres_virtual = var->xres;
> +	var->yres_virtual = var->yres;
> +	var->xoffset = 0;
> +	var->yoffset = 0;
> +
> +        /* Check bits per pixel. */
> +        if (!var->bits_per_pixel)
> +		var->bits_per_pixel = default_bits_per_pixel(
> +		     var->xres_virtual, var->yres_virtual, info->fix.smem_len);
> +	else if (var->bits_per_pixel != 16 &&
> +		 var->bits_per_pixel != 32)
> +		return -EINVAL;
> +        if (!bits_per_pixel_fits(var->xres_virtual, var->yres_virtual,
> +			var->bits_per_pixel, info->fix.smem_len))
> +		var->bits_per_pixel = default_bits_per_pixel(
> +		     var->xres_virtual, var->yres_virtual, info->fix.smem_len);
> +        if (!bits_per_pixel_fits(var->xres_virtual, var->yres_virtual,
> +			var->bits_per_pixel, info->fix.smem_len))
> +		return -ENOMEM;
> +	if (var->bits_per_pixel == 16) {
> +		var->red    = (struct fb_bitfield){ .offset =  0, .length = 5 };
> +		var->green  = (struct fb_bitfield){ .offset =  5, .length = 5 };
> +		var->blue   = (struct fb_bitfield){ .offset = 10, .length = 5 };
> +		var->transp = (struct fb_bitfield){ .offset = 15, .length = 1 };
> +	} else if (var->bits_per_pixel == 32) {
> +		var->red    = (struct fb_bitfield){ .offset =  0, .length = 8 };
> +		var->green  = (struct fb_bitfield){ .offset =  8, .length = 8 };
> +		var->blue   = (struct fb_bitfield){ .offset = 16, .length = 8 };
> +		var->transp = (struct fb_bitfield){ .offset = 24, .length = 8 };
> +	} else
> +		return -EINVAL;		/* Unsupported bits per pixel. */
> +
> +        /* Screen rotations are not supported. */
> +	if (var->rotate)
> +		return -EINVAL;
> +
> +        return 0;
> +}
> +
> +static int ps2fb_cb_check_var(
> +	struct fb_var_screeninfo *var, struct fb_info *info)
> +{
> +	struct ps2fb_par *par = info->par;
> +	unsigned long flags;
> +	int err;
> +
> +	spin_lock_irqsave(&par->lock, flags);
> +	err = ps2fb_check_var(var, info);
> +	spin_unlock_irqrestore(&par->lock, flags);
> +
> +	if (!err && info->tileops)
> +		if (info->tileops->fb_get_tilemax(info) < 256)
> +			err = -ENOMEM;
> +
> +	return err;
> +}
> +
> +static u32 block_dimensions(u32 dim, u32 alignment)
> +{
> +	u32 mask = 0;
> +	u32 d;
> +
> +	for (d = 1; d <= dim; d++)
> +		if (d % alignment == 0)
> +			mask |= 1 << (d - 1);
> +
> +	return mask;
> +}
> +
> +static int init_console_buffer(struct platform_device *pdev,
> +	struct fb_info *info)
> +{
> +	static struct fb_ops fbops = {
> +		.owner		= THIS_MODULE,
> +		.fb_check_var	= ps2fb_cb_check_var,
> +	};
> +
> +	static struct fb_tile_ops tileops = {
> +		.fb_get_tilemax = ps2fb_cb_get_tilemax
> +	};
> +
> +	struct ps2fb_par *par = info->par;
> +
> +	fb_info(info, "Graphics Synthesizer console frame buffer device\n");
> +
> +	info->screen_size = 0;
> +	info->screen_base = NULL;	/* mmap is unsupported by hardware */
> +
> +	info->fix.smem_start = 0;	/* GS frame buffer is local memory */
> +	info->fix.smem_len = GS_MEMORY_SIZE;
> +
> +	info->fbops = &fbops;
> +	info->flags = FBINFO_DEFAULT |
> +		      FBINFO_READS_FAST;
> +
> +	info->flags |= FBINFO_MISC_TILEBLITTING;
> +	info->tileops = &tileops;
> +
> +	/*
> +	 * BITBLTBUF for pixel format CT32 requires divisibility by 2,
> +	 * and CT16 requires divisibility by 4. So 4 is a safe choice.
> +	 */
> +	info->pixmap.blit_x = block_dimensions(GS_PSMT4_BLOCK_WIDTH, 4);
> +	info->pixmap.blit_y = block_dimensions(GS_PSMT4_BLOCK_HEIGHT, 1);
> +
> +	/* 8x8 default font tile size for fb_get_tilemax */
> +	par->cb.tile = cb_tile(8, 8);
> +
> +	return 0;
> +}
> +
> +static int ps2fb_probe(struct platform_device *pdev)
> +{
> +	struct ps2fb_par *par;
> +	struct fb_info *info;
> +	int err;
> +
> +	info = framebuffer_alloc(sizeof(*par), &pdev->dev);
> +	if (info == NULL) {
> +		dev_err(&pdev->dev, "framebuffer_alloc failed\n");
> +		err = -ENOMEM;
> +		goto err_framebuffer_alloc;
> +	}
> +
> +	par = info->par;
> +
> +	spin_lock_init(&par->lock);
> +
> +	par->package.buffer = (union package *)__get_free_page(GFP_DMA);
> +	if (!par->package.buffer) {
> +		dev_err(&pdev->dev, "Failed to allocate package buffer\n");
> +		err = -ENOMEM;
> +		goto err_package_buffer;
> +	}
> +	par->package.capacity = PAGE_SIZE / sizeof(union package);
> +
> +	strlcpy(info->fix.id, "PS2 GS", ARRAY_SIZE(info->fix.id));
> +	info->fix.accel = FB_ACCEL_PLAYSTATION_2;
> +
> +	err = init_console_buffer(pdev, info);
> +	if (err < 0)
> +		goto err_init_buffer;
> +
> +	info->mode = &par->mode;
> +
> +	if (register_framebuffer(info) < 0) {
> +		fb_err(info, "register_framebuffer failed\n");
> +		err = -EINVAL;
> +		goto err_register_framebuffer;
> +	}
> +
> +	platform_set_drvdata(pdev, info);
> +
> +	return 0;
> +
> +err_register_framebuffer:
> +err_init_buffer:
> +	free_page((unsigned long)par->package.buffer);
> +err_package_buffer:
> +	framebuffer_release(info);
> +err_framebuffer_alloc:
> +	return err;
> +}
> +
> +static int ps2fb_remove(struct platform_device *pdev)
> +{
> +	struct fb_info *info = platform_get_drvdata(pdev);
> +	struct ps2fb_par *par = info->par;
> +	int err = 0;
> +
> +	if (info != NULL) {
> +		unregister_framebuffer(info);
> +		fb_dealloc_cmap(&info->cmap);
> +
> +		framebuffer_release(info);
> +	}
> +
> +	if (!gif_wait()) {
> +		fb_err(info, "Failed to complete GIF DMA transfer\n");
> +		err = -EBUSY;
> +	}
> +	free_page((unsigned long)par->package.buffer);
> +
> +	return err;
> +}
> +
> +static struct platform_driver ps2fb_driver = {
> +	.probe		= ps2fb_probe,
> +	.remove		= ps2fb_remove,
> +	.driver = {
> +		.name	= DEVICE_NAME,
> +	},
> +};
> +
> +static struct platform_device *ps2fb_device;
> +
> +static int __init ps2fb_init(void)
> +{
> +	int err;
> +
> +#ifndef MODULE
> +	char *options = NULL;
> +	char *this_opt;
> +
> +	if (fb_get_options(DEVICE_NAME, &options))
> +		return -ENODEV;
> +	if (!options || !*options)
> +		goto no_options;
> +
> +	while ((this_opt = strsep(&options, ",")) != NULL) {
> +		if (!*this_opt)
> +			continue;
> +
> +		if (!strncmp(this_opt, "mode_option:", 12))
> +			mode_option = &this_opt[12];
> +		else if ('0' <= this_opt[0] && this_opt[0] <= '9')
> +			mode_option = this_opt;
> +		else
> +			pr_warn(DEVICE_NAME ": Unrecognized option \"%s\"\n",
> +				this_opt);
> +	}
> +
> +no_options:
> +#endif /* !MODULE */
> +
> +	/* Default to a suitable PAL or NTSC broadcast mode. */
> +	if (!mode_option)
> +		mode_option = gs_region_pal() ? "576x460i@50" : "576x384i@60";
> +
> +	ps2fb_device = platform_device_alloc("ps2fb", 0);
> +	if (!ps2fb_device)
> +		return -ENOMEM;
> +
> +	err = platform_device_add(ps2fb_device);
> +	if (err < 0) {
> +		platform_device_put(ps2fb_device);
> +		return err;
> +	}
> +
> +	return platform_driver_register(&ps2fb_driver);
> +}
> +
> +static void __exit ps2fb_exit(void)
> +{
> +	platform_driver_unregister(&ps2fb_driver);
> +	platform_device_unregister(ps2fb_device);
> +}
> +
> +module_init(ps2fb_init);
> +module_exit(ps2fb_exit);
> +
> +module_param(mode_option, charp, 0);
> +MODULE_PARM_DESC(mode_option,
> +	"Specify initial video mode as \"<xres>x<yres>[-<bpp>][@<refresh>]\"");
> +
> +MODULE_DESCRIPTION("PlayStation 2 frame buffer driver");
> +MODULE_AUTHOR("Fredrik Noring");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
> index 24d4c16e3ae0..cb562672cc3a 100644
> --- a/include/linux/console_struct.h
> +++ b/include/linux/console_struct.h
> @@ -13,9 +13,11 @@
>   #ifndef _LINUX_CONSOLE_STRUCT_H
>   #define _LINUX_CONSOLE_STRUCT_H
>   
> +#include <linux/tty.h>
>   #include <linux/wait.h>
>   #include <linux/vt.h>
>   #include <linux/workqueue.h>
> +#include <uapi/linux/kd.h>
>   
>   struct uni_pagedir;
>   struct uni_screen;
> diff --git a/include/uapi/linux/fb.h b/include/uapi/linux/fb.h
> index b6aac7ee1f67..38d88eebf651 100644
> --- a/include/uapi/linux/fb.h
> +++ b/include/uapi/linux/fb.h
> @@ -149,6 +149,7 @@
>   #define FB_ACCEL_SUPERSAVAGE    0x8c    /* S3 Supersavage               */
>   #define FB_ACCEL_PROSAVAGE_DDR  0x8d	/* S3 ProSavage DDR             */
>   #define FB_ACCEL_PROSAVAGE_DDRK 0x8e	/* S3 ProSavage DDR-K           */
> +#define FB_ACCEL_PLAYSTATION_2  0x8f	/* PlayStation 2                */
>   
>   #define FB_ACCEL_PUV3_UNIGFX	0xa0	/* PKUnity-v3 Unigfx		*/
>
Fredrik Noring Sept. 2, 2019, 2:40 p.m. UTC | #2
Hi Jiaxun Yang,

> According to kernel policy[1] no more new FBDev driver would be accepted.
> 
> Please refactor it to DRM.

I had the impression that the DRM does not support a kernel console?

Standard hardware, on its part, does not have a serial port or similar
alternatives that could easily function as a console, unfortunately.

Fredrik
Aaro Koskinen Sept. 2, 2019, 5:47 p.m. UTC | #3
Hi,

On Mon, Sep 02, 2019 at 04:40:07PM +0200, Fredrik Noring wrote:
> Hi Jiaxun Yang,
> 
> > According to kernel policy[1] no more new FBDev driver would be accepted.
> > 
> > Please refactor it to DRM.
> 
> I had the impression that the DRM does not support a kernel console?

DRM drivers do provide FB console.

A.
Jiaxun Yang Sept. 3, 2019, 4:01 a.m. UTC | #4
在 2019/9/2 22:40, Fredrik Noring 写道:
> Hi Jiaxun Yang,
>
>> According to kernel policy[1] no more new FBDev driver would be accepted.
>>
>> Please refactor it to DRM.
> I had the impression that the DRM does not support a kernel console?

Hi Fredik,

DRM do have fb_helper providing FB console.

I think refactor your current FB driver with drm gem cma helper won't be 
very hard.

>
> Standard hardware, on its part, does not have a serial port or similar
> alternatives that could easily function as a console, unfortunately.

Ahh, the guy runs Linux on that gaming console is really cool! How do 
you debug your code?

Thanks.

--
Jiaxun Yang
Fredrik Noring Sept. 3, 2019, 2:32 p.m. UTC | #5
Hi Aaro,

> DRM drivers do provide FB console.

Great, thanks! I will look into that.

Fredrik
Fredrik Noring Sept. 3, 2019, 5:42 p.m. UTC | #6
> DRM do have fb_helper providing FB console.
> 
> I think refactor your current FB driver with drm gem cma helper won't be 
> very hard.

Thanks! I will update that for v2.

> Ahh, the guy runs Linux on that gaming console is really cool! How do 
> you debug your code?

The video screen is used for the earliest stages, even before the kernel
is decompressed in arch/mips/boot/compressed/decompress.c. I have a simple
patch that sets up the Graphics Synthesizer and uses DMA to render a
trivial frame buffer console, displaying

	zimage at:     00803BE0 00BDDE8C
	Uncompressing Linux at load address 80010000
	Now, booting the kernel...

and so on as a video image. This is functionally equivalent to the serial
port drivers for prom_putchar() that other MIPS platforms have, and
essential to debug early boot and kexec.

[ I assume this code will not be accepted, since the approach is rather
nontraditional, which is why I didn't include it in the patch set. ]

Somewhat later the fbcon driver takes over the video screen console. Then
USB is configured with a wifi device, an ssh daemon is started etc. for
remote login and scripting. 

I have QEMU patches for R5900 user mode emulation, that can be used for
development and debugging. I mainly use it to compile Gentoo for the
PlayStation 2.

Fredrik
Maciej W. Rozycki Sept. 3, 2019, 5:59 p.m. UTC | #7
On Tue, 3 Sep 2019, Fredrik Noring wrote:

> The video screen is used for the earliest stages, even before the kernel
> is decompressed in arch/mips/boot/compressed/decompress.c. I have a simple
> patch that sets up the Graphics Synthesizer and uses DMA to render a
> trivial frame buffer console, displaying
> 
> 	zimage at:     00803BE0 00BDDE8C
> 	Uncompressing Linux at load address 80010000
> 	Now, booting the kernel...
> 
> and so on as a video image. This is functionally equivalent to the serial
> port drivers for prom_putchar() that other MIPS platforms have, and
> essential to debug early boot and kexec.
> 
> [ I assume this code will not be accepted, since the approach is rather
> nontraditional, which is why I didn't include it in the patch set. ]

 Hmm, why do you think this would be unacceptable?  Do you expect your 
code to cause unjustified maintenance burden once integrated?

  Maciej
Fredrik Noring Sept. 3, 2019, 6:46 p.m. UTC | #8
>  Hmm, why do you think this would be unacceptable?  Do you expect your 
> code to cause unjustified maintenance burden once integrated?

I suppose it can be implemented quite nicely, with a bit of effort. Steps
to be taken:

1. A font needs to be linked with the early boot code. I'm using
   acorndata_8x8 from lib/fonts/font_acorn_8x8.c, but any font would do.

2. A single, or small set of, video resolutions need to be selected. I
   chose 1920x1080p and wrote those values to the Graphics Synthesizer
   video clock registers. A complete video mode line parser seems
   excessive, unless that code can be shared with the boot code somehow.

3. Implement clear_screen() and plot(x, y) for a single pixel using DMA
   commands to the Graphics Synthesizer. [ Clearing the screen can be
   done by plotting the whole screen, but it was somewhat slow. ]

4. Implement prom_putchar() using plot() for the given font.

Steps (1) and (4) could be generic code.

Fredrik
Fredrik Noring Dec. 13, 2020, 1:20 p.m. UTC | #9
On Mon, Sep 02, 2019 at 09:12:22AM +0800, Jiaxun Yang wrote:
> According to kernel policy[1] no more new FBDev driver would be accepted.
> 
> Please refactor it to DRM.

I obtained a functional DRM driver a while back, but unfortunately it's
a poor fit on the hardware, for the following reasons, in brief:

The DRM subsystem (in general) breaks user-space in significant ways:

- fbset command timing mode settings are ignored;
- attempts to set modes with /sys/class/graphics/fb0/mode are ignored;
- /sys/class/graphics/fb0/modes outputs incorrect refresh rates;
- relevant modes are missing in /sys/class/graphics/fb0/modes;
- the standard DRM video modes seem to mix up the sync and front porch
  timings in struct drm_display_mode but this remains to be confirmed.

DRM seems to rely heavily on EDID to negotiate a video resolution, but
EDID isn't available with vintage analogue display hardware.

The Graphics Synthesizer (GS) hardware is essentially a serial device
that accepts sequences of commands with data via DMA. Notably, it
cannot be memory mapped.

Can the GS be modelled, in a reasonable way, on existing kernel video
interfaces, when taking memory efficiency and performance into account?

For anyone who is interested:

In addition to this patch series and its documentation references,
some background information and details on the Graphics Synthesizer
are available here:

https://github.com/frno7/linux/issues/10

Fredrik
Fredrik Noring Jan. 29, 2022, 11:23 a.m. UTC | #10
Hi Helge,

Thank you for maintaining fbdev! The PlayStation 2 port makes heavy use of
it and its acceleration features[1], including tiling operations:

	info->flags = FBINFO_DEFAULT |
		      FBINFO_HWACCEL_COPYAREA |
		      FBINFO_HWACCEL_FILLRECT |
		      FBINFO_HWACCEL_IMAGEBLIT |
		      FBINFO_HWACCEL_XPAN |
		      FBINFO_HWACCEL_YPAN |
		      FBINFO_HWACCEL_YWRAP |
		      FBINFO_PARTIAL_PAN_OK |
		      FBINFO_READS_FAST;

	info->flags |= FBINFO_MISC_TILEBLITTING;
	info->tileops = &tileops;

I attempted a DRM port[2], but my impression is that the DRM subsystem has
grave problems in that

- fbset command timing mode settings are ignored;
- attempts to set modes with /sys/class/graphics/fb0/mode are ignored;
- /sys/class/graphics/fb0/modes outputs incorrect refresh rates and types;
- relevant modes are missing in /sys/class/graphics/fb0/modes;
- the standard DRM video modes seem to mix up the sync and front porch
  timings in struct drm_display_mode but this remains to be confirmed;
- the DRM seems to rely heavily on EDID to negotiate a video resolution,
  but EDID isn't available with vintage analogue display hardware.

This is only for a (fast and efficient) text console.

My impression is also that the DRM wouldn't do more than displaying a text
console, and perhaps an inefficient virtual frame buffer, due to the nature
of the Graphics Synthesizer hardware. The complete set of features of the
Graphics Synthesizer, and related vector and image processors, seem to go
beyond what the DRM subsystem can offer in its current form. I admit that
there may possibilities that I haven't seen yet, though. :-)

Some background on the Graphics Synthesizer can be found in [3], as well
as in official documentation[4][5]. Many of its command primitives are
defined in [6][7], and there is more to it with the data paths from
various co-processors, etc.

Note: The fbdev patch below was, at the time, submitted in a series of
18 patches related to fbdev for PlayStation 2. See the complete ps2fb.c[1].

References:

[1] https://github.com/frno7/linux/blob/ps2-main/drivers/video/fbdev/ps2fb.c
[2] https://github.com/frno7/linux/tree/ps2-main/drivers/gpu/drm/gs
[3] https://github.com/frno7/linux/wiki/The-Graphics-Synthesizer
[4] EE User's Manual, version 6.0, Sony Computer Entertainment Inc.
[5] GS User's Manual, version 6.0, Sony Computer Entertainment Inc.
[6] https://github.com/frno7/linux/blob/ps2-main/arch/mips/include/uapi/asm/gs.h
[7] https://github.com/frno7/linux/blob/ps2-main/arch/mips/include/uapi/asm/gif.h

Fredrik

On Sunday, 1 September 2019, Fredrik Noring wrote:
> The main limitation is the lack of mmap, since the Graphics Synthesizer
> has local frame buffer memory that is not directly accessible from the
> main bus. The GS has 4 MiB of local memory.
> 
> The console drawing primitives are synchronous to allow printk at any
> time. This is highly useful for debugging but it is not the fastest
> possible implementation. The console is nevertheless very fast and
> makes use of several hardware accelerated features of the Graphics
> Synthesizer.
> 
> The maximum practical resolution is 1920x1080p at 16 bits per pixel that
> requires 4147200 bytes of local memory, leaving 47104 bytes for a tiled
> font, which at 8x8 pixels and a minimum 4 bits indexed texture palette is
> at most 1464 characters. The indexed palette makes switching colours easy.
> &struct fb_tile_ops is accelerated with GS texture sprites that are fast
> (GS local copy) for the kernel via simple DMA GS commands via the GIF.
> 
> Signed-off-by: Fredrik Noring <noring@nocrew.org>
> ---
>  drivers/video/fbdev/Kconfig    |  12 +
>  drivers/video/fbdev/Makefile   |   1 +
>  drivers/video/fbdev/ps2fb.c    | 533 +++++++++++++++++++++++++++++++++
>  include/linux/console_struct.h |   2 +
>  include/uapi/linux/fb.h        |   1 +
>  5 files changed, 549 insertions(+)
>  create mode 100644 drivers/video/fbdev/ps2fb.c
> 
> diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
> index 6b2de93bd302..cc93cbd67b01 100644
> --- a/drivers/video/fbdev/Kconfig
> +++ b/drivers/video/fbdev/Kconfig
> @@ -1999,6 +1999,18 @@ config FB_IBM_GXT4500
>  	  doesn't use Geometry Engine GT1000. This driver also supports
>  	  AGP Fire GL2/3/4 cards on x86.
>  
> +# FIXME FB_SYS_*
> +config FB_PS2
> +	tristate "Frame buffer driver for Sony Playstation 2"
> +	depends on FB && SONY_PS2
> +	select PS2_GS
> +	select FB_TILEBLITTING
> +	default y
> +	help
> +	  Frame buffer driver for the Sony Playstation 2 Graphics Synthesizer.
> +	  Memory mapping is not supported since the frame buffer is local to
> +	  the GS.
> +
>  config FB_PS3
>  	tristate "PS3 GPU framebuffer driver"
>  	depends on FB && PS3_PS3AV
> diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
> index 7dc4861a93e6..1e55fa8ca4af 100644
> --- a/drivers/video/fbdev/Makefile
> +++ b/drivers/video/fbdev/Makefile
> @@ -105,6 +105,7 @@ obj-$(CONFIG_FB_S3C2410)	  += s3c2410fb.o
>  obj-$(CONFIG_FB_FSL_DIU)	  += fsl-diu-fb.o
>  obj-$(CONFIG_FB_COBALT)           += cobalt_lcdfb.o
>  obj-$(CONFIG_FB_IBM_GXT4500)	  += gxt4500.o
> +obj-$(CONFIG_FB_PS2)		  += ps2fb.o
>  obj-$(CONFIG_FB_PS3)		  += ps3fb.o
>  obj-$(CONFIG_FB_SM501)            += sm501fb.o
>  obj-$(CONFIG_FB_UDL)		  += udlfb.o
> diff --git a/drivers/video/fbdev/ps2fb.c b/drivers/video/fbdev/ps2fb.c
> new file mode 100644
> index 000000000000..7bfbc3c2aa4d
> --- /dev/null
> +++ b/drivers/video/fbdev/ps2fb.c
> @@ -0,0 +1,533 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * PlayStation 2 frame buffer driver
> + *
> + * Copyright (C) 2019 Fredrik Noring
> + */
> +
> +/**
> + * DOC: The PlayStation 2 frame buffer console
> + *
> + * The frame buffer supports a tiled frame buffer console. The main limitation
> + * is the lack of memory mapping (mmap), since the Graphics Synthesizer has
> + * local frame buffer memory that is not directly accessible from the main bus.
> + * The GS has 4 MiB of local memory.
> + *
> + * The console drawing primitives are synchronous to allow printk at any time.
> + * This is highly useful for debugging but it is not the fastest possible
> + * implementation. The console is nevertheless very fast and makes use of
> + * several hardware accelerated features of the Graphics Synthesizer.
> + *
> + * The maximum practical resolution is 1920x1080p at 16 bits per pixel that
> + * requires 4147200 bytes of local memory, leaving 47104 bytes for a tiled
> + * font, which at 8x8 pixels and a minimum 4 bits indexed texture palette is
> + * at most 1464 characters. The indexed palette makes switching colours easy.
> + * &struct fb_tile_ops is accelerated with GS texture sprites that are fast
> + * (GS local copy) for the kernel via simple DMA GS commands via the GIF.
> + *
> + * The local memory is organised as follows: first comes the display buffer,
> + * then one block of a palette, and finally the font installed as a texture.
> + *
> + * All frame buffer transmissions are done by DMA via GIF PATH3.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/errno.h>
> +#include <linux/fb.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/string.h>
> +#include <linux/uaccess.h>
> +
> +#include <asm/io.h>
> +
> +#include <asm/mach-ps2/dmac.h>
> +#include <asm/mach-ps2/gif.h>
> +#include <asm/mach-ps2/gs.h>
> +#include <asm/mach-ps2/gs-registers.h>
> +
> +#include <uapi/asm/gif.h>
> +#include <uapi/asm/gs.h>
> +
> +#define DEVICE_NAME "ps2fb"
> +
> +#define PALETTE_BLOCK_COUNT 1	/* One block is used for the indexed colors */
> +
> +/* Module parameters */
> +static char *mode_option;
> +
> +union package {
> +	union gif_data gif;
> +	struct dma_tag dma;
> +};
> +
> +/**
> + * struct tile_texture - texture representing a tile
> + * @tbp: texture base pointer
> + * @u: texel u coordinate (x coordinate)
> + * @v: texel v coordinate (y coordinate)
> + */
> +struct tile_texture {
> +	u32 tbp;
> +	u32 u;
> +	u32 v;
> +};
> +
> +/**
> + * struct console_buffer - console buffer
> + * @block_count: number of frame buffer blocks
> + * @bg: background color index
> + * @fg: foreground color index
> + * @tile: tile dimensions
> + * @tile.width: width in pixels
> + * @tile.height: height in pixels
> + * @tile.width2: least width in pixels, power of 2
> + * @tile.height2: least height in pixels, power of 2
> + * @tile.block: tiles are stored as textures in the PSMT4 pixel storage format
> + * 	with both cols and rows as powers of 2
> + * @tile.block.cols: tile columns per GS block
> + * @tile.block.rows: tile rows per GS block
> + */
> +struct console_buffer {
> +	u32 block_count;
> +
> +	u32 bg;
> +	u32 fg;
> +
> +	struct cb_tile {
> +		u32 width;
> +		u32 height;
> +
> +		u32 width2;
> +		u32 height2;
> +
> +		struct {
> +			u32 cols;
> +			u32 rows;
> +		} block;
> +	} tile;
> +};
> +
> +/**
> + * struct ps2fb_par - driver specific structure
> + * @lock: spin lock to be taken for all structure operations
> + * @cb: console buffer definition
> + * @package: tags and datafor the GIF
> + * @package.capacity: maximum number of GIF packages in 16-byte unit
> + * @package.buffer: DMA buffer for GIF packages
> + */
> +struct ps2fb_par {
> +	spinlock_t lock;
> +
> +	struct console_buffer cb;
> +
> +	struct {
> +		size_t capacity;
> +		union package *buffer;
> +	} package;
> +};
> +
> +/**
> + * texture_least_power_of_2 - round up to a power of 2, not less than 8
> + * @n: integer to round up
> + *
> + * Return: least integer that is a power of 2 and not less than @n or 8
> + */
> +static u32 texture_least_power_of_2(u32 n)
> +{
> +	return max(1 << get_count_order(n), 8);
> +}
> +
> +/**
> + * cb_tile - create a console buffer tile object
> + * @width: width of tile in pixels
> + * @height: height of tile in pixels
> + *
> + * Return: a console buffer tile object with the given width and height
> + */
> +static struct cb_tile cb_tile(u32 width, u32 height)
> +{
> +	const u32 width2 = texture_least_power_of_2(width);
> +	const u32 height2 = texture_least_power_of_2(height);
> +
> +	return (struct cb_tile) {
> +		.width = width,
> +		.height = height,
> +
> +		.width2 = width2,
> +		.height2 = height2,
> +
> +		.block = {
> +			.cols = GS_PSMT4_BLOCK_WIDTH / width2,
> +			.rows = GS_PSMT4_BLOCK_HEIGHT / height2,
> +		},
> +	};
> +}
> +
> +/**
> + * display_buffer_size - display buffer size for a given video resolution
> + *
> + * This calculation is a lower bound estimate. A precise calculation would have
> + * to take memory pages, blocks and column arrangements into account. To choose
> + * the appropriate standard video mode such details can be disregarded, though.
> + *
> + * Return: the size in bytes of the display buffer
> + */
> +static u32 display_buffer_size(const u32 xres_virtual, const u32 yres_virtual,
> +      const u32 bits_per_pixel)
> +{
> +	return (xres_virtual * yres_virtual * bits_per_pixel) / 8;
> +}
> +
> +/**
> + * ps2fb_cb_get_tilemax - maximum number of tiles
> + * @info: frame buffer info object
> + *
> + * Return: the maximum number of tiles
> + */
> +static int ps2fb_cb_get_tilemax(struct fb_info *info)
> +{
> +	const struct ps2fb_par *par = info->par;
> +	const u32 block_tile_count =
> +		par->cb.tile.block.cols *
> +		par->cb.tile.block.rows;
> +	const s32 blocks_available =
> +		GS_BLOCK_COUNT - par->cb.block_count - PALETTE_BLOCK_COUNT;
> +
> +	return blocks_available > 0 ? blocks_available * block_tile_count : 0;
> +}
> +
> +/**
> + * bits_per_pixel_fits - does the given resolution fit the given buffer size?
> + * @xres_virtual: virtual x resolution in pixels
> + * @yres_virtual: virtual y resolution in pixels
> + * @bits_per_pixel: number of bits per pixel
> + * @buffer_size: size in bytes of display buffer
> + *
> + * The size calculation is approximate, but accurate enough for the standard
> + * video modes.
> + *
> + * Return: %true if the resolution fits the given buffer size, otherwise %false
> + */
> +static bool bits_per_pixel_fits(const u32 xres_virtual, const u32 yres_virtual,
> +      const int bits_per_pixel, const size_t buffer_size)
> +{
> +	return display_buffer_size(xres_virtual, yres_virtual,
> +		bits_per_pixel) <= buffer_size;
> +}
> +
> +/**
> + * default_bits_per_pixel - choose either 16 or 32 bits per pixel
> + * @xres_virtual: virtual x resolution in pixels
> + * @yres_virtual: virtual y resolution in pixels
> + * @buffer_size: size in bytes of display buffer
> + *
> + * 32 bits per pixel is returned unless this does not fit the given buffer size.
> + *
> + * The size calculation is approximate, but accurate enough for the standard
> + * video modes.
> + *
> + * Return: 16 or 32 bits per pixel
> + */
> +static int default_bits_per_pixel(
> +	const u32 xres_virtual, const u32 yres_virtual,
> +	const size_t buffer_size)
> +{
> +	return bits_per_pixel_fits(xres_virtual, yres_virtual,
> +		32, buffer_size) ? 32 : 16;
> +}
> +
> +/**
> + * filled_var_videomode - is the screen info video mode filled in?
> + * @var: screen info object to check
> + *
> + * Return: %true if the video mode is filled in, otherwise %false
> + */
> +static bool filled_var_videomode(const struct fb_var_screeninfo *var)
> +{
> +	return var->xres > 0 && var->hsync_len > 0 &&
> +	       var->yres > 0 && var->vsync_len > 0 && var->pixclock > 0;
> +}
> +
> +static int ps2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
> +{
> +	/* Check whether video mode defaults are needed. */
> +	if (!filled_var_videomode(var)) {
> +		const struct fb_videomode *vm =
> +			fb_find_best_mode(var, &info->modelist);
> +
> +		if (!vm)
> +			return -EINVAL;
> +
> +		fb_videomode_to_var(var, vm);
> +	}
> +
> +        /* GS video register resolution is limited to 2048. */
> +        if (var->xres < 1 || 2048 < var->xres ||
> +	    var->yres < 1 || 2048 < var->yres)
> +		return -EINVAL;
> +
> +	var->xres_virtual = var->xres;
> +	var->yres_virtual = var->yres;
> +	var->xoffset = 0;
> +	var->yoffset = 0;
> +
> +        /* Check bits per pixel. */
> +        if (!var->bits_per_pixel)
> +		var->bits_per_pixel = default_bits_per_pixel(
> +		     var->xres_virtual, var->yres_virtual, info->fix.smem_len);
> +	else if (var->bits_per_pixel != 16 &&
> +		 var->bits_per_pixel != 32)
> +		return -EINVAL;
> +        if (!bits_per_pixel_fits(var->xres_virtual, var->yres_virtual,
> +			var->bits_per_pixel, info->fix.smem_len))
> +		var->bits_per_pixel = default_bits_per_pixel(
> +		     var->xres_virtual, var->yres_virtual, info->fix.smem_len);
> +        if (!bits_per_pixel_fits(var->xres_virtual, var->yres_virtual,
> +			var->bits_per_pixel, info->fix.smem_len))
> +		return -ENOMEM;
> +	if (var->bits_per_pixel == 16) {
> +		var->red    = (struct fb_bitfield){ .offset =  0, .length = 5 };
> +		var->green  = (struct fb_bitfield){ .offset =  5, .length = 5 };
> +		var->blue   = (struct fb_bitfield){ .offset = 10, .length = 5 };
> +		var->transp = (struct fb_bitfield){ .offset = 15, .length = 1 };
> +	} else if (var->bits_per_pixel == 32) {
> +		var->red    = (struct fb_bitfield){ .offset =  0, .length = 8 };
> +		var->green  = (struct fb_bitfield){ .offset =  8, .length = 8 };
> +		var->blue   = (struct fb_bitfield){ .offset = 16, .length = 8 };
> +		var->transp = (struct fb_bitfield){ .offset = 24, .length = 8 };
> +	} else
> +		return -EINVAL;		/* Unsupported bits per pixel. */
> +
> +        /* Screen rotations are not supported. */
> +	if (var->rotate)
> +		return -EINVAL;
> +
> +        return 0;
> +}
> +
> +static int ps2fb_cb_check_var(
> +	struct fb_var_screeninfo *var, struct fb_info *info)
> +{
> +	struct ps2fb_par *par = info->par;
> +	unsigned long flags;
> +	int err;
> +
> +	spin_lock_irqsave(&par->lock, flags);
> +	err = ps2fb_check_var(var, info);
> +	spin_unlock_irqrestore(&par->lock, flags);
> +
> +	if (!err && info->tileops)
> +		if (info->tileops->fb_get_tilemax(info) < 256)
> +			err = -ENOMEM;
> +
> +	return err;
> +}
> +
> +static u32 block_dimensions(u32 dim, u32 alignment)
> +{
> +	u32 mask = 0;
> +	u32 d;
> +
> +	for (d = 1; d <= dim; d++)
> +		if (d % alignment == 0)
> +			mask |= 1 << (d - 1);
> +
> +	return mask;
> +}
> +
> +static int init_console_buffer(struct platform_device *pdev,
> +	struct fb_info *info)
> +{
> +	static struct fb_ops fbops = {
> +		.owner		= THIS_MODULE,
> +		.fb_check_var	= ps2fb_cb_check_var,
> +	};
> +
> +	static struct fb_tile_ops tileops = {
> +		.fb_get_tilemax = ps2fb_cb_get_tilemax
> +	};
> +
> +	struct ps2fb_par *par = info->par;
> +
> +	fb_info(info, "Graphics Synthesizer console frame buffer device\n");
> +
> +	info->screen_size = 0;
> +	info->screen_base = NULL;	/* mmap is unsupported by hardware */
> +
> +	info->fix.smem_start = 0;	/* GS frame buffer is local memory */
> +	info->fix.smem_len = GS_MEMORY_SIZE;
> +
> +	info->fbops = &fbops;
> +	info->flags = FBINFO_DEFAULT |
> +		      FBINFO_READS_FAST;
> +
> +	info->flags |= FBINFO_MISC_TILEBLITTING;
> +	info->tileops = &tileops;
> +
> +	/*
> +	 * BITBLTBUF for pixel format CT32 requires divisibility by 2,
> +	 * and CT16 requires divisibility by 4. So 4 is a safe choice.
> +	 */
> +	info->pixmap.blit_x = block_dimensions(GS_PSMT4_BLOCK_WIDTH, 4);
> +	info->pixmap.blit_y = block_dimensions(GS_PSMT4_BLOCK_HEIGHT, 1);
> +
> +	/* 8x8 default font tile size for fb_get_tilemax */
> +	par->cb.tile = cb_tile(8, 8);
> +
> +	return 0;
> +}
> +
> +static int ps2fb_probe(struct platform_device *pdev)
> +{
> +	struct ps2fb_par *par;
> +	struct fb_info *info;
> +	int err;
> +
> +	info = framebuffer_alloc(sizeof(*par), &pdev->dev);
> +	if (info == NULL) {
> +		dev_err(&pdev->dev, "framebuffer_alloc failed\n");
> +		err = -ENOMEM;
> +		goto err_framebuffer_alloc;
> +	}
> +
> +	par = info->par;
> +
> +	spin_lock_init(&par->lock);
> +
> +	par->package.buffer = (union package *)__get_free_page(GFP_DMA);
> +	if (!par->package.buffer) {
> +		dev_err(&pdev->dev, "Failed to allocate package buffer\n");
> +		err = -ENOMEM;
> +		goto err_package_buffer;
> +	}
> +	par->package.capacity = PAGE_SIZE / sizeof(union package);
> +
> +	strlcpy(info->fix.id, "PS2 GS", ARRAY_SIZE(info->fix.id));
> +	info->fix.accel = FB_ACCEL_PLAYSTATION_2;
> +
> +	err = init_console_buffer(pdev, info);
> +	if (err < 0)
> +		goto err_init_buffer;
> +
> +	info->mode = &par->mode;
> +
> +	if (register_framebuffer(info) < 0) {
> +		fb_err(info, "register_framebuffer failed\n");
> +		err = -EINVAL;
> +		goto err_register_framebuffer;
> +	}
> +
> +	platform_set_drvdata(pdev, info);
> +
> +	return 0;
> +
> +err_register_framebuffer:
> +err_init_buffer:
> +	free_page((unsigned long)par->package.buffer);
> +err_package_buffer:
> +	framebuffer_release(info);
> +err_framebuffer_alloc:
> +	return err;
> +}
> +
> +static int ps2fb_remove(struct platform_device *pdev)
> +{
> +	struct fb_info *info = platform_get_drvdata(pdev);
> +	struct ps2fb_par *par = info->par;
> +	int err = 0;
> +
> +	if (info != NULL) {
> +		unregister_framebuffer(info);
> +		fb_dealloc_cmap(&info->cmap);
> +
> +		framebuffer_release(info);
> +	}
> +
> +	if (!gif_wait()) {
> +		fb_err(info, "Failed to complete GIF DMA transfer\n");
> +		err = -EBUSY;
> +	}
> +	free_page((unsigned long)par->package.buffer);
> +
> +	return err;
> +}
> +
> +static struct platform_driver ps2fb_driver = {
> +	.probe		= ps2fb_probe,
> +	.remove		= ps2fb_remove,
> +	.driver = {
> +		.name	= DEVICE_NAME,
> +	},
> +};
> +
> +static struct platform_device *ps2fb_device;
> +
> +static int __init ps2fb_init(void)
> +{
> +	int err;
> +
> +#ifndef MODULE
> +	char *options = NULL;
> +	char *this_opt;
> +
> +	if (fb_get_options(DEVICE_NAME, &options))
> +		return -ENODEV;
> +	if (!options || !*options)
> +		goto no_options;
> +
> +	while ((this_opt = strsep(&options, ",")) != NULL) {
> +		if (!*this_opt)
> +			continue;
> +
> +		if (!strncmp(this_opt, "mode_option:", 12))
> +			mode_option = &this_opt[12];
> +		else if ('0' <= this_opt[0] && this_opt[0] <= '9')
> +			mode_option = this_opt;
> +		else
> +			pr_warn(DEVICE_NAME ": Unrecognized option \"%s\"\n",
> +				this_opt);
> +	}
> +
> +no_options:
> +#endif /* !MODULE */
> +
> +	/* Default to a suitable PAL or NTSC broadcast mode. */
> +	if (!mode_option)
> +		mode_option = gs_region_pal() ? "576x460i@50" : "576x384i@60";
> +
> +	ps2fb_device = platform_device_alloc("ps2fb", 0);
> +	if (!ps2fb_device)
> +		return -ENOMEM;
> +
> +	err = platform_device_add(ps2fb_device);
> +	if (err < 0) {
> +		platform_device_put(ps2fb_device);
> +		return err;
> +	}
> +
> +	return platform_driver_register(&ps2fb_driver);
> +}
> +
> +static void __exit ps2fb_exit(void)
> +{
> +	platform_driver_unregister(&ps2fb_driver);
> +	platform_device_unregister(ps2fb_device);
> +}
> +
> +module_init(ps2fb_init);
> +module_exit(ps2fb_exit);
> +
> +module_param(mode_option, charp, 0);
> +MODULE_PARM_DESC(mode_option,
> +	"Specify initial video mode as \"<xres>x<yres>[-<bpp>][@<refresh>]\"");
> +
> +MODULE_DESCRIPTION("PlayStation 2 frame buffer driver");
> +MODULE_AUTHOR("Fredrik Noring");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
> index 24d4c16e3ae0..cb562672cc3a 100644
> --- a/include/linux/console_struct.h
> +++ b/include/linux/console_struct.h
> @@ -13,9 +13,11 @@
>  #ifndef _LINUX_CONSOLE_STRUCT_H
>  #define _LINUX_CONSOLE_STRUCT_H
>  
> +#include <linux/tty.h>
>  #include <linux/wait.h>
>  #include <linux/vt.h>
>  #include <linux/workqueue.h>
> +#include <uapi/linux/kd.h>
>  
>  struct uni_pagedir;
>  struct uni_screen;
> diff --git a/include/uapi/linux/fb.h b/include/uapi/linux/fb.h
> index b6aac7ee1f67..38d88eebf651 100644
> --- a/include/uapi/linux/fb.h
> +++ b/include/uapi/linux/fb.h
> @@ -149,6 +149,7 @@
>  #define FB_ACCEL_SUPERSAVAGE    0x8c    /* S3 Supersavage               */
>  #define FB_ACCEL_PROSAVAGE_DDR  0x8d	/* S3 ProSavage DDR             */
>  #define FB_ACCEL_PROSAVAGE_DDRK 0x8e	/* S3 ProSavage DDR-K           */
> +#define FB_ACCEL_PLAYSTATION_2  0x8f	/* PlayStation 2                */
>  
>  #define FB_ACCEL_PUV3_UNIGFX	0xa0	/* PKUnity-v3 Unigfx		*/
>  
> -- 
> 2.21.0
>
diff mbox series

Patch

diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
index 6b2de93bd302..cc93cbd67b01 100644
--- a/drivers/video/fbdev/Kconfig
+++ b/drivers/video/fbdev/Kconfig
@@ -1999,6 +1999,18 @@  config FB_IBM_GXT4500
 	  doesn't use Geometry Engine GT1000. This driver also supports
 	  AGP Fire GL2/3/4 cards on x86.
 
+# FIXME FB_SYS_*
+config FB_PS2
+	tristate "Frame buffer driver for Sony Playstation 2"
+	depends on FB && SONY_PS2
+	select PS2_GS
+	select FB_TILEBLITTING
+	default y
+	help
+	  Frame buffer driver for the Sony Playstation 2 Graphics Synthesizer.
+	  Memory mapping is not supported since the frame buffer is local to
+	  the GS.
+
 config FB_PS3
 	tristate "PS3 GPU framebuffer driver"
 	depends on FB && PS3_PS3AV
diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
index 7dc4861a93e6..1e55fa8ca4af 100644
--- a/drivers/video/fbdev/Makefile
+++ b/drivers/video/fbdev/Makefile
@@ -105,6 +105,7 @@  obj-$(CONFIG_FB_S3C2410)	  += s3c2410fb.o
 obj-$(CONFIG_FB_FSL_DIU)	  += fsl-diu-fb.o
 obj-$(CONFIG_FB_COBALT)           += cobalt_lcdfb.o
 obj-$(CONFIG_FB_IBM_GXT4500)	  += gxt4500.o
+obj-$(CONFIG_FB_PS2)		  += ps2fb.o
 obj-$(CONFIG_FB_PS3)		  += ps3fb.o
 obj-$(CONFIG_FB_SM501)            += sm501fb.o
 obj-$(CONFIG_FB_UDL)		  += udlfb.o
diff --git a/drivers/video/fbdev/ps2fb.c b/drivers/video/fbdev/ps2fb.c
new file mode 100644
index 000000000000..7bfbc3c2aa4d
--- /dev/null
+++ b/drivers/video/fbdev/ps2fb.c
@@ -0,0 +1,533 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PlayStation 2 frame buffer driver
+ *
+ * Copyright (C) 2019 Fredrik Noring
+ */
+
+/**
+ * DOC: The PlayStation 2 frame buffer console
+ *
+ * The frame buffer supports a tiled frame buffer console. The main limitation
+ * is the lack of memory mapping (mmap), since the Graphics Synthesizer has
+ * local frame buffer memory that is not directly accessible from the main bus.
+ * The GS has 4 MiB of local memory.
+ *
+ * The console drawing primitives are synchronous to allow printk at any time.
+ * This is highly useful for debugging but it is not the fastest possible
+ * implementation. The console is nevertheless very fast and makes use of
+ * several hardware accelerated features of the Graphics Synthesizer.
+ *
+ * The maximum practical resolution is 1920x1080p at 16 bits per pixel that
+ * requires 4147200 bytes of local memory, leaving 47104 bytes for a tiled
+ * font, which at 8x8 pixels and a minimum 4 bits indexed texture palette is
+ * at most 1464 characters. The indexed palette makes switching colours easy.
+ * &struct fb_tile_ops is accelerated with GS texture sprites that are fast
+ * (GS local copy) for the kernel via simple DMA GS commands via the GIF.
+ *
+ * The local memory is organised as follows: first comes the display buffer,
+ * then one block of a palette, and finally the font installed as a texture.
+ *
+ * All frame buffer transmissions are done by DMA via GIF PATH3.
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include <asm/io.h>
+
+#include <asm/mach-ps2/dmac.h>
+#include <asm/mach-ps2/gif.h>
+#include <asm/mach-ps2/gs.h>
+#include <asm/mach-ps2/gs-registers.h>
+
+#include <uapi/asm/gif.h>
+#include <uapi/asm/gs.h>
+
+#define DEVICE_NAME "ps2fb"
+
+#define PALETTE_BLOCK_COUNT 1	/* One block is used for the indexed colors */
+
+/* Module parameters */
+static char *mode_option;
+
+union package {
+	union gif_data gif;
+	struct dma_tag dma;
+};
+
+/**
+ * struct tile_texture - texture representing a tile
+ * @tbp: texture base pointer
+ * @u: texel u coordinate (x coordinate)
+ * @v: texel v coordinate (y coordinate)
+ */
+struct tile_texture {
+	u32 tbp;
+	u32 u;
+	u32 v;
+};
+
+/**
+ * struct console_buffer - console buffer
+ * @block_count: number of frame buffer blocks
+ * @bg: background color index
+ * @fg: foreground color index
+ * @tile: tile dimensions
+ * @tile.width: width in pixels
+ * @tile.height: height in pixels
+ * @tile.width2: least width in pixels, power of 2
+ * @tile.height2: least height in pixels, power of 2
+ * @tile.block: tiles are stored as textures in the PSMT4 pixel storage format
+ * 	with both cols and rows as powers of 2
+ * @tile.block.cols: tile columns per GS block
+ * @tile.block.rows: tile rows per GS block
+ */
+struct console_buffer {
+	u32 block_count;
+
+	u32 bg;
+	u32 fg;
+
+	struct cb_tile {
+		u32 width;
+		u32 height;
+
+		u32 width2;
+		u32 height2;
+
+		struct {
+			u32 cols;
+			u32 rows;
+		} block;
+	} tile;
+};
+
+/**
+ * struct ps2fb_par - driver specific structure
+ * @lock: spin lock to be taken for all structure operations
+ * @cb: console buffer definition
+ * @package: tags and datafor the GIF
+ * @package.capacity: maximum number of GIF packages in 16-byte unit
+ * @package.buffer: DMA buffer for GIF packages
+ */
+struct ps2fb_par {
+	spinlock_t lock;
+
+	struct console_buffer cb;
+
+	struct {
+		size_t capacity;
+		union package *buffer;
+	} package;
+};
+
+/**
+ * texture_least_power_of_2 - round up to a power of 2, not less than 8
+ * @n: integer to round up
+ *
+ * Return: least integer that is a power of 2 and not less than @n or 8
+ */
+static u32 texture_least_power_of_2(u32 n)
+{
+	return max(1 << get_count_order(n), 8);
+}
+
+/**
+ * cb_tile - create a console buffer tile object
+ * @width: width of tile in pixels
+ * @height: height of tile in pixels
+ *
+ * Return: a console buffer tile object with the given width and height
+ */
+static struct cb_tile cb_tile(u32 width, u32 height)
+{
+	const u32 width2 = texture_least_power_of_2(width);
+	const u32 height2 = texture_least_power_of_2(height);
+
+	return (struct cb_tile) {
+		.width = width,
+		.height = height,
+
+		.width2 = width2,
+		.height2 = height2,
+
+		.block = {
+			.cols = GS_PSMT4_BLOCK_WIDTH / width2,
+			.rows = GS_PSMT4_BLOCK_HEIGHT / height2,
+		},
+	};
+}
+
+/**
+ * display_buffer_size - display buffer size for a given video resolution
+ *
+ * This calculation is a lower bound estimate. A precise calculation would have
+ * to take memory pages, blocks and column arrangements into account. To choose
+ * the appropriate standard video mode such details can be disregarded, though.
+ *
+ * Return: the size in bytes of the display buffer
+ */
+static u32 display_buffer_size(const u32 xres_virtual, const u32 yres_virtual,
+      const u32 bits_per_pixel)
+{
+	return (xres_virtual * yres_virtual * bits_per_pixel) / 8;
+}
+
+/**
+ * ps2fb_cb_get_tilemax - maximum number of tiles
+ * @info: frame buffer info object
+ *
+ * Return: the maximum number of tiles
+ */
+static int ps2fb_cb_get_tilemax(struct fb_info *info)
+{
+	const struct ps2fb_par *par = info->par;
+	const u32 block_tile_count =
+		par->cb.tile.block.cols *
+		par->cb.tile.block.rows;
+	const s32 blocks_available =
+		GS_BLOCK_COUNT - par->cb.block_count - PALETTE_BLOCK_COUNT;
+
+	return blocks_available > 0 ? blocks_available * block_tile_count : 0;
+}
+
+/**
+ * bits_per_pixel_fits - does the given resolution fit the given buffer size?
+ * @xres_virtual: virtual x resolution in pixels
+ * @yres_virtual: virtual y resolution in pixels
+ * @bits_per_pixel: number of bits per pixel
+ * @buffer_size: size in bytes of display buffer
+ *
+ * The size calculation is approximate, but accurate enough for the standard
+ * video modes.
+ *
+ * Return: %true if the resolution fits the given buffer size, otherwise %false
+ */
+static bool bits_per_pixel_fits(const u32 xres_virtual, const u32 yres_virtual,
+      const int bits_per_pixel, const size_t buffer_size)
+{
+	return display_buffer_size(xres_virtual, yres_virtual,
+		bits_per_pixel) <= buffer_size;
+}
+
+/**
+ * default_bits_per_pixel - choose either 16 or 32 bits per pixel
+ * @xres_virtual: virtual x resolution in pixels
+ * @yres_virtual: virtual y resolution in pixels
+ * @buffer_size: size in bytes of display buffer
+ *
+ * 32 bits per pixel is returned unless this does not fit the given buffer size.
+ *
+ * The size calculation is approximate, but accurate enough for the standard
+ * video modes.
+ *
+ * Return: 16 or 32 bits per pixel
+ */
+static int default_bits_per_pixel(
+	const u32 xres_virtual, const u32 yres_virtual,
+	const size_t buffer_size)
+{
+	return bits_per_pixel_fits(xres_virtual, yres_virtual,
+		32, buffer_size) ? 32 : 16;
+}
+
+/**
+ * filled_var_videomode - is the screen info video mode filled in?
+ * @var: screen info object to check
+ *
+ * Return: %true if the video mode is filled in, otherwise %false
+ */
+static bool filled_var_videomode(const struct fb_var_screeninfo *var)
+{
+	return var->xres > 0 && var->hsync_len > 0 &&
+	       var->yres > 0 && var->vsync_len > 0 && var->pixclock > 0;
+}
+
+static int ps2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	/* Check whether video mode defaults are needed. */
+	if (!filled_var_videomode(var)) {
+		const struct fb_videomode *vm =
+			fb_find_best_mode(var, &info->modelist);
+
+		if (!vm)
+			return -EINVAL;
+
+		fb_videomode_to_var(var, vm);
+	}
+
+        /* GS video register resolution is limited to 2048. */
+        if (var->xres < 1 || 2048 < var->xres ||
+	    var->yres < 1 || 2048 < var->yres)
+		return -EINVAL;
+
+	var->xres_virtual = var->xres;
+	var->yres_virtual = var->yres;
+	var->xoffset = 0;
+	var->yoffset = 0;
+
+        /* Check bits per pixel. */
+        if (!var->bits_per_pixel)
+		var->bits_per_pixel = default_bits_per_pixel(
+		     var->xres_virtual, var->yres_virtual, info->fix.smem_len);
+	else if (var->bits_per_pixel != 16 &&
+		 var->bits_per_pixel != 32)
+		return -EINVAL;
+        if (!bits_per_pixel_fits(var->xres_virtual, var->yres_virtual,
+			var->bits_per_pixel, info->fix.smem_len))
+		var->bits_per_pixel = default_bits_per_pixel(
+		     var->xres_virtual, var->yres_virtual, info->fix.smem_len);
+        if (!bits_per_pixel_fits(var->xres_virtual, var->yres_virtual,
+			var->bits_per_pixel, info->fix.smem_len))
+		return -ENOMEM;
+	if (var->bits_per_pixel == 16) {
+		var->red    = (struct fb_bitfield){ .offset =  0, .length = 5 };
+		var->green  = (struct fb_bitfield){ .offset =  5, .length = 5 };
+		var->blue   = (struct fb_bitfield){ .offset = 10, .length = 5 };
+		var->transp = (struct fb_bitfield){ .offset = 15, .length = 1 };
+	} else if (var->bits_per_pixel == 32) {
+		var->red    = (struct fb_bitfield){ .offset =  0, .length = 8 };
+		var->green  = (struct fb_bitfield){ .offset =  8, .length = 8 };
+		var->blue   = (struct fb_bitfield){ .offset = 16, .length = 8 };
+		var->transp = (struct fb_bitfield){ .offset = 24, .length = 8 };
+	} else
+		return -EINVAL;		/* Unsupported bits per pixel. */
+
+        /* Screen rotations are not supported. */
+	if (var->rotate)
+		return -EINVAL;
+
+        return 0;
+}
+
+static int ps2fb_cb_check_var(
+	struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	struct ps2fb_par *par = info->par;
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&par->lock, flags);
+	err = ps2fb_check_var(var, info);
+	spin_unlock_irqrestore(&par->lock, flags);
+
+	if (!err && info->tileops)
+		if (info->tileops->fb_get_tilemax(info) < 256)
+			err = -ENOMEM;
+
+	return err;
+}
+
+static u32 block_dimensions(u32 dim, u32 alignment)
+{
+	u32 mask = 0;
+	u32 d;
+
+	for (d = 1; d <= dim; d++)
+		if (d % alignment == 0)
+			mask |= 1 << (d - 1);
+
+	return mask;
+}
+
+static int init_console_buffer(struct platform_device *pdev,
+	struct fb_info *info)
+{
+	static struct fb_ops fbops = {
+		.owner		= THIS_MODULE,
+		.fb_check_var	= ps2fb_cb_check_var,
+	};
+
+	static struct fb_tile_ops tileops = {
+		.fb_get_tilemax = ps2fb_cb_get_tilemax
+	};
+
+	struct ps2fb_par *par = info->par;
+
+	fb_info(info, "Graphics Synthesizer console frame buffer device\n");
+
+	info->screen_size = 0;
+	info->screen_base = NULL;	/* mmap is unsupported by hardware */
+
+	info->fix.smem_start = 0;	/* GS frame buffer is local memory */
+	info->fix.smem_len = GS_MEMORY_SIZE;
+
+	info->fbops = &fbops;
+	info->flags = FBINFO_DEFAULT |
+		      FBINFO_READS_FAST;
+
+	info->flags |= FBINFO_MISC_TILEBLITTING;
+	info->tileops = &tileops;
+
+	/*
+	 * BITBLTBUF for pixel format CT32 requires divisibility by 2,
+	 * and CT16 requires divisibility by 4. So 4 is a safe choice.
+	 */
+	info->pixmap.blit_x = block_dimensions(GS_PSMT4_BLOCK_WIDTH, 4);
+	info->pixmap.blit_y = block_dimensions(GS_PSMT4_BLOCK_HEIGHT, 1);
+
+	/* 8x8 default font tile size for fb_get_tilemax */
+	par->cb.tile = cb_tile(8, 8);
+
+	return 0;
+}
+
+static int ps2fb_probe(struct platform_device *pdev)
+{
+	struct ps2fb_par *par;
+	struct fb_info *info;
+	int err;
+
+	info = framebuffer_alloc(sizeof(*par), &pdev->dev);
+	if (info == NULL) {
+		dev_err(&pdev->dev, "framebuffer_alloc failed\n");
+		err = -ENOMEM;
+		goto err_framebuffer_alloc;
+	}
+
+	par = info->par;
+
+	spin_lock_init(&par->lock);
+
+	par->package.buffer = (union package *)__get_free_page(GFP_DMA);
+	if (!par->package.buffer) {
+		dev_err(&pdev->dev, "Failed to allocate package buffer\n");
+		err = -ENOMEM;
+		goto err_package_buffer;
+	}
+	par->package.capacity = PAGE_SIZE / sizeof(union package);
+
+	strlcpy(info->fix.id, "PS2 GS", ARRAY_SIZE(info->fix.id));
+	info->fix.accel = FB_ACCEL_PLAYSTATION_2;
+
+	err = init_console_buffer(pdev, info);
+	if (err < 0)
+		goto err_init_buffer;
+
+	info->mode = &par->mode;
+
+	if (register_framebuffer(info) < 0) {
+		fb_err(info, "register_framebuffer failed\n");
+		err = -EINVAL;
+		goto err_register_framebuffer;
+	}
+
+	platform_set_drvdata(pdev, info);
+
+	return 0;
+
+err_register_framebuffer:
+err_init_buffer:
+	free_page((unsigned long)par->package.buffer);
+err_package_buffer:
+	framebuffer_release(info);
+err_framebuffer_alloc:
+	return err;
+}
+
+static int ps2fb_remove(struct platform_device *pdev)
+{
+	struct fb_info *info = platform_get_drvdata(pdev);
+	struct ps2fb_par *par = info->par;
+	int err = 0;
+
+	if (info != NULL) {
+		unregister_framebuffer(info);
+		fb_dealloc_cmap(&info->cmap);
+
+		framebuffer_release(info);
+	}
+
+	if (!gif_wait()) {
+		fb_err(info, "Failed to complete GIF DMA transfer\n");
+		err = -EBUSY;
+	}
+	free_page((unsigned long)par->package.buffer);
+
+	return err;
+}
+
+static struct platform_driver ps2fb_driver = {
+	.probe		= ps2fb_probe,
+	.remove		= ps2fb_remove,
+	.driver = {
+		.name	= DEVICE_NAME,
+	},
+};
+
+static struct platform_device *ps2fb_device;
+
+static int __init ps2fb_init(void)
+{
+	int err;
+
+#ifndef MODULE
+	char *options = NULL;
+	char *this_opt;
+
+	if (fb_get_options(DEVICE_NAME, &options))
+		return -ENODEV;
+	if (!options || !*options)
+		goto no_options;
+
+	while ((this_opt = strsep(&options, ",")) != NULL) {
+		if (!*this_opt)
+			continue;
+
+		if (!strncmp(this_opt, "mode_option:", 12))
+			mode_option = &this_opt[12];
+		else if ('0' <= this_opt[0] && this_opt[0] <= '9')
+			mode_option = this_opt;
+		else
+			pr_warn(DEVICE_NAME ": Unrecognized option \"%s\"\n",
+				this_opt);
+	}
+
+no_options:
+#endif /* !MODULE */
+
+	/* Default to a suitable PAL or NTSC broadcast mode. */
+	if (!mode_option)
+		mode_option = gs_region_pal() ? "576x460i@50" : "576x384i@60";
+
+	ps2fb_device = platform_device_alloc("ps2fb", 0);
+	if (!ps2fb_device)
+		return -ENOMEM;
+
+	err = platform_device_add(ps2fb_device);
+	if (err < 0) {
+		platform_device_put(ps2fb_device);
+		return err;
+	}
+
+	return platform_driver_register(&ps2fb_driver);
+}
+
+static void __exit ps2fb_exit(void)
+{
+	platform_driver_unregister(&ps2fb_driver);
+	platform_device_unregister(ps2fb_device);
+}
+
+module_init(ps2fb_init);
+module_exit(ps2fb_exit);
+
+module_param(mode_option, charp, 0);
+MODULE_PARM_DESC(mode_option,
+	"Specify initial video mode as \"<xres>x<yres>[-<bpp>][@<refresh>]\"");
+
+MODULE_DESCRIPTION("PlayStation 2 frame buffer driver");
+MODULE_AUTHOR("Fredrik Noring");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index 24d4c16e3ae0..cb562672cc3a 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -13,9 +13,11 @@ 
 #ifndef _LINUX_CONSOLE_STRUCT_H
 #define _LINUX_CONSOLE_STRUCT_H
 
+#include <linux/tty.h>
 #include <linux/wait.h>
 #include <linux/vt.h>
 #include <linux/workqueue.h>
+#include <uapi/linux/kd.h>
 
 struct uni_pagedir;
 struct uni_screen;
diff --git a/include/uapi/linux/fb.h b/include/uapi/linux/fb.h
index b6aac7ee1f67..38d88eebf651 100644
--- a/include/uapi/linux/fb.h
+++ b/include/uapi/linux/fb.h
@@ -149,6 +149,7 @@ 
 #define FB_ACCEL_SUPERSAVAGE    0x8c    /* S3 Supersavage               */
 #define FB_ACCEL_PROSAVAGE_DDR  0x8d	/* S3 ProSavage DDR             */
 #define FB_ACCEL_PROSAVAGE_DDRK 0x8e	/* S3 ProSavage DDR-K           */
+#define FB_ACCEL_PLAYSTATION_2  0x8f	/* PlayStation 2                */
 
 #define FB_ACCEL_PUV3_UNIGFX	0xa0	/* PKUnity-v3 Unigfx		*/